Browse Source

Aug 29: [FIX] Bug Fixed 'pos_chatter'

pull/338/merge
Risvana Cybro 3 weeks ago
parent
commit
6781cbb227
  1. 1
      pos_chatter/__init__.py
  2. 41
      pos_chatter/controllers/systray.py
  3. 1
      pos_chatter/models/__init__.py
  4. 29
      pos_chatter/models/mail_message.py
  5. BIN
      pos_chatter/static/description/assets/screenshots/1.png
  6. BIN
      pos_chatter/static/description/assets/screenshots/2.png
  7. 2
      pos_chatter/static/description/index.html
  8. 307
      pos_chatter/static/src/js/pos_msg_view.js
  9. 69
      pos_chatter/static/src/js/pos_systray_icon.js
  10. 7
      pos_chatter/static/src/xml/pos_systray_icon.xml

1
pos_chatter/__init__.py

@ -19,3 +19,4 @@
# If not, see <http://www.gnu.org/licenses/>.
################################################################################
from . import controllers
from . import models

41
pos_chatter/controllers/systray.py

@ -38,21 +38,31 @@ class SystrayController(http.Controller):
@http.route('/pos_systray/message_data', auth='public', type='json')
def get_data_pos_systray(self):
"""
Summary:
Getting data to the Message chatter list.
Return:
it contains details about chatter list.
"""
return [{'id': mail_channel_id.id,
'type': mail_channel_id.channel_type,
'name': mail_channel_id.name,
'message_body': request.env['mail.message'].search(
[('model', '=', 'discuss.channel'),
('res_id', '=', mail_channel_id.id)], limit=1).body
} for mail_channel_id in request.env['discuss.channel'].search([])
for partner_id in mail_channel_id.channel_partner_ids
if partner_id.id == request.env.user.partner_id.id]
channels = request.env['discuss.channel'].search([])
partner_id = request.env.user.partner_id.id
data = []
for mail_channel in channels:
if partner_id in mail_channel.channel_partner_ids.ids:
message = request.env['mail.message'].search(
[('model', '=', 'discuss.channel'),
('res_id', '=', mail_channel.id)],
limit=1, order="id desc"
)
messages = request.env['mail.message'].search_count(
[('model', '=', 'discuss.channel'),
('res_id', '=', mail_channel.id),
('is_read', '=', False)]
)
data.append({
'message_id': message.id,
'id': mail_channel.id,
'type': mail_channel.channel_type,
'name': mail_channel.name,
'message_body': message.body if message else "",
'count': messages
})
return data
@http.route('/pos_systray/chat_message', auth='public', type='json')
def get_data_chat_box(self, **kw):
@ -105,6 +115,7 @@ class SystrayController(http.Controller):
'model': 'discuss.channel',
'res_id': int(data['res_id']),
'message_type': 'comment',
'is_read': True,
'author_id': request.env.user.partner_id.id
})
return True

1
pos_chatter/models/__init__.py

@ -0,0 +1 @@
from . import mail_message

29
pos_chatter/models/mail_message.py

@ -0,0 +1,29 @@
from odoo import models, fields , api
class MailMessage(models.Model):
_inherit = "mail.message"
is_read = fields.Boolean(string="Read", default=False)
@api.model
def compute_read_message(self, datas):
print(datas)
try:
messages = self.env['mail.message'].search([
('model', '=', 'discuss.channel'),
('res_id', '=', datas),
('is_read', '=', False)
])
for message in messages:
if message.is_read is False:
message.write({'is_read': True})
print(f"Marked messages as read: {message.ids}")
else:
print("No unread messages found.")
except Exception as e:
print(f"An error occurred: {str(e)}")

BIN
pos_chatter/static/description/assets/screenshots/1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 130 KiB

BIN
pos_chatter/static/description/assets/screenshots/2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 122 KiB

2
pos_chatter/static/description/index.html

@ -161,7 +161,7 @@
<div class="px-3">
<h4 class="mt-2"
style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important">
Chat list.</h4>
Chat list with unread message counts.</h4>
</div>
</div>
</div>

307
pos_chatter/static/src/js/pos_msg_view.js

@ -1,87 +1,126 @@
/** @odoo-module **/
import { jsonrpc } from "@web/core/network/rpc_service";
const { mount,xml, onMounted, useState, useRef} = owl;
import { ChatMsgView } from "./pos_chat_view"
const { mount, xml, onMounted, useState, useRef } = owl;
import { ChatMsgView } from "./pos_chat_view";
export class PosMsgView extends owl.Component {
setup(){
super.setup()
this.root = useRef("root")
onMounted(this.render_msg_view)
setup() {
super.setup();
this.root = useRef("root");
onMounted(this.render_msg_view);
this.MsgWindow = new ChatMsgView();
this.state = useState({
'data': {}
data: [],
counts: {
all: 0,
chat: 0,
channels: 0
}
});
}
/** for getting all chat list */
render_msg_view(data){
var self = this
jsonrpc('/pos_systray/message_data',).then(function(data){
var message_list = []
var messages = data
$(messages).each(function(message){
const htmlString = messages[message].message_body;
/** Fetch all chat messages */
render_msg_view() {
const self = this;
jsonrpc("/pos_systray/message_data").then((data) => {
const message_list = data.map((message) => {
const parser = new DOMParser();
const parsedHtml = parser.parseFromString(htmlString, 'text/html');
const plainText = parsedHtml.documentElement.textContent;
message_list.push({
'id': messages[message].id,
'type': messages[message].type,
'name': messages[message].name,
'message_body': plainText
})
const parsedHtml = parser.parseFromString(message.message_body, "text/html");
let plainText = parsedHtml.documentElement.textContent;
// Format SMS failure messages
if (message.type === 'sms_failure') {
plainText = `SMS Failure: Contact\nAn error occurred when sending an SMS\nAug 18`;
}
return {
id: message.id,
type: message.type,
name: message.name,
message_body: plainText,
count: message.count || 0,
};
});
self.state.data = message_list
})
// Calculate counts for each category
const counts = {
chat: message_list.filter(m => m.type === 'chat').reduce((sum, msg) => sum + msg.count, 0),
all: message_list.filter(m => m.type === 'channel').reduce((sum, msg) => sum + msg.count, 0),
channels: message_list.reduce((sum, msg) => sum + msg.count, 0),
};
self.state.data = message_list;
self.state.counts = counts;
}).catch((error) => {
console.error("Error fetching messages:", error);
alert("Failed to load messages. Please try again.");
});
}
/** for click function for navbar*/
_onClickAllMessage(){
this.root.el.querySelector('#all_message').style.display = "block"
this.root.el.querySelector('#all_chat').style.display = "none"
this.root.el.querySelector('#all_channels').style.display = "none"
this.root.el.querySelector('#all_message_button').style.color = "#000"
this.root.el.querySelector('#all_chat_button').style.color = "#9c9a97"
this.root.el.querySelector('#all_channels_button').style.color = "#9c9a97"
/** Toggle visibility between All, Chat, and Channels */
toggleView(activeButtonId, activeSectionId) {
["all_message", "all_chat", "all_channels"].forEach((id) => {
const element = this.root.el.querySelector(`#${id}`);
if (element) {
element.style.display = id === activeSectionId ? "block" : "none";
}
});
["all_message_button", "all_chat_button", "all_channels_button"].forEach((id) => {
const element = this.root.el.querySelector(`#${id}`);
if (element) {
element.style.color = id === activeButtonId ? "#000" : "#9c9a97";
}
});
}
_onClickAllMessage() {
this.toggleView("all_message_button", "all_message");
}
/** for click function for navbar*/
_onClickAllChannels(){
this.root.el.querySelector('#all_message').style.display = "none"
this.root.el.querySelector('#all_chat').style.display = "none"
this.root.el.querySelector('#all_channels').style.display = "block"
this.root.el.querySelector('#all_message_button').style.color = "#9c9a97"
this.root.el.querySelector('#all_chat_button').style.color = "#9c9a97"
this.root.el.querySelector('#all_channels_button').style.color = "#000"
_onClickAllChannels() {
this.toggleView("all_channels_button", "all_channels");
}
/** for click function for navbar*/
_onClickAllChat(){
this.root.el.querySelector('#all_message').style.display = "none"
this.root.el.querySelector('#all_chat').style.display = "block"
this.root.el.querySelector('#all_channels').style.display = "none"
this.root.el.querySelector('#all_message_button').style.color = "#9c9a97"
this.root.el.querySelector('#all_chat_button').style.color = "#000"
this.root.el.querySelector('#all_channels_button').style.color = "#9c9a97"
_onClickAllChat() {
this.toggleView("all_chat_button", "all_chat");
}
/** On clicking the chat or channel open the corresponding chat view */
_onClickToMessage(ev){
var self = this
var channel_id = ev.currentTarget.getAttribute("value")
this.__owl__.remove()
if($("#pos_chat_view").length == 0){
this.schedule_dropdown = mount(ChatMsgView, document.body, {props: {
channel_id
}})
}else if($("#pos_chat_view").length > 0){
this.schedule_dropdown = mount(ChatMsgView, document.body, {props: {
channel_id
}})
/** Open chat view */
_onClickToMessage(ev) {
const channel_id = ev.currentTarget.getAttribute("value");
if (!channel_id || isNaN(parseInt(channel_id))) {
console.error("Invalid channel_id:", channel_id);
alert("Cannot open chat: Invalid message ID");
return;
}
const self = this;
jsonrpc("/web/dataset/call_kw/mail.message/compute_read_message", {
model: "mail.message",
method: "compute_read_message",
args: [parseInt(channel_id)],
kwargs: {}
}).then(() => {
self.__owl__.remove();
self.schedule_dropdown = mount(ChatMsgView, document.body, { props: { channel_id: parseInt(channel_id) } });
}).catch((error) => {
console.error("RPC Error Details:", {
message: error.message,
data: error.data,
type: error.type,
stack: error.stack
});
alert("Failed to load chat. Please try again.");
});
}
}
PosMsgView.template = xml`
//template for pos messages list view
<div class="pos_systray_template" t-ref="root"
style="height:auto;width:350px;background-color:white;position:fixed;right:5px;top:49px;">
<div style="display:flex;height: 27px;">
PosMsgView.template = xml`
<div class="pos_systray_template" t-ref="root"
style="height:auto;width:350px;background-color:#f3f3f3;position:fixed;right:5px;top:49px;padding:10px;margin: 7px 14px;">
<div style="display:flex;height: 27px;position:relative;">
<div style="display:flex;">
<p style="margin-left:10px;cursor: pointer;" id="all_message_button"
t-on-click="_onClickAllMessage">All</p>
<p style="margin-left:10px;cursor: pointer;color:#9c9a97;" id="all_chat_button"
@ -89,62 +128,96 @@ PosMsgView.template = xml`
<p style="margin-left:10px;cursor: pointer;color:#9c9a97;" id="all_channels_button"
t-on-click="_onClickAllChannels">Channels</p>
</div>
<hr/>
<div id="all_message">
<t t-foreach="state.data" t-as="data" t-key="data['id']">
<span t-if="state.counts.all > 0"
style="position:absolute;right:220px;top:-18px;background:#2196F3;color:white;border-radius:50%;
width:18px;height:18px;font-size:10px;text-align:center;line-height:18px;"
t-esc="state.counts.all"/>
<span t-if="state.counts.chat > 0"
style="position:absolute;right:267px;top:-19px;background:#04AA6D;color:white;border-radius:50%;
width:18px;height:18px;font-size:10px;text-align:center;line-height:18px;"
t-esc="state.counts.chat"/>
<span t-if="state.counts.channels > 0"
style="position:absolute;right:302px;top:-19px;background:#ffeb3b;color:black;border-radius:50%;
width:18px;height:18px;font-size:10px;text-align:center;line-height:18px;"
t-esc="state.counts.channels"/>
</div>
<hr/>
<div id="all_message">
<t t-foreach="state.data" t-as="data" t-key="data.id">
<div style="background-color: #e7f3fe;border-left: 6px solid #2196F3;
margin-bottom: 15px;padding: 4px 12px;display:flex;cursor:pointer;" t-att-value="data['id']"
t-on-click="_onClickToMessage">
<div style="width:30px">
<t t-if="data['type'] == 'channel'">
<i style="margin:40%" class="fa fa-users"/>
</t>
<t t-else="">
<i style="margin:40%" class="fa fa-user"/>
</t>
</div>
margin-bottom: 15px;padding: 4px 12px;display:flex;cursor:pointer;position:relative;" t-att-value="data.id"
t-on-click="_onClickToMessage">
<div style="width:30px">
<t t-if="data.type == 'channel'">
<i style="margin:40%" class="fa fa-users"/>
</t>
<t t-elif="data.type == 'sms_failure'">
<i style="margin:40%" class="fa fa-exclamation-triangle"/>
</t>
<t t-else="">
<i style="margin:40%" class="fa fa-user"/>
</t>
</div>
<div style="margin-left: 20px;width: 250px">
<span t-esc="data['name']"/>
<br/>
<small style="color:#9c9a97;" t-raw="data['message_body']"/>
<div style="display:flex;justify-content:space-between;">
<span t-esc="data.name"/>
<t t-if="data.count > 0">
<span style="background-color:#2196F3;color:white;border-radius:50%;
width:18px;height:18px;font-size:10px;text-align:center;
line-height:18px;" t-esc="data.count"/>
</t>
</div>
<small style="color:#9c9a97; white-space: pre-line;" t-raw="data.message_body"/>
</div>
</div>
</t>
</div>
<div id="all_chat" style="display:none">
<t t-foreach="state.data" t-as="data" t-key="data['id']">
<t t-if="data['type'] == 'chat'">
<div style="background-color: #ddffdd; border-left: 6px solid #04AA6D;
margin-bottom: 15px;padding: 4px 12px;display:flex;cursor:pointer;" t-att-value="data['id']"
t-on-click="_onClickToMessage">
<div style="width:30px">
<i style="margin:8px" class="fa fa-user"/>
</div>
<div style="margin-left: 20px;width: 250px">
<span t-esc="data['name']"/>
<br/>
<small style="color:#9c9a97;" t-raw="data['message_body']"/>
</t>
</div>
<div id="all_chat" style="display:none">
<t t-foreach="state.data" t-as="data" t-key="data.id">
<t t-if="data.type == 'chat'">
<div style="background-color: #ddffdd;border-left: 6px solid #04AA6D;
margin-bottom: 15px;padding: 4px 12px;display:flex;cursor:pointer;position:relative;" t-att-value="data.id"
t-on-click="_onClickToMessage">
<div style="width:30px">
<i style="margin:8px" class="fa fa-user"/>
</div>
<div style="margin-left: 20px;width: 250px">
<div style="display:flex;justify-content:space-between;">
<span t-esc="data.name"/>
<t t-if="data.count > 0">
<span style="background-color:#04AA6D;color:white;border-radius:50%;
width:18px;height:18px;font-size:10px;text-align:center;
line-height:18px;" t-esc="data.count"/>
</t>
</div>
<small style="color:#9c9a97; white-space: pre-line;" t-raw="data.message_body"/>
</div>
</t>
</div>
</t>
</div>
<div id="all_channels" style="display:none">
<t t-foreach="state.data" t-as="data" t-key="data['id']">
<t t-if="data['type'] == 'channel'">
<div style="background-color: #ffffcc;border-left: 6px solid #ffeb3b;
margin-bottom: 15px;padding: 4px 12px;display:flex;cursor:pointer;" t-att-value="data['id']"
t-on-click="_onClickToMessage">
<div style="width:30px">
<i style="margin:8px" class="fa fa-users"/>
</div>
<div style="margin-left: 20px;width: 250px">
<span t-esc="data['name']"/>
<br/>
<small style="color:#9c9a97;" t-raw="data['message_body']"/>
</t>
</div>
<div id="all_channels" style="display:none">
<t t-foreach="state.data" t-as="data" t-key="data.id">
<t t-if="data.type == 'channel'">
<div style="background-color: #ffffcc;border-left: 6px solid #ffeb3b;
margin-bottom: 15px;padding: 4px 12px;display:flex;cursor:pointer;position:relative;" t-att-value="data.id"
t-on-click="_onClickToMessage">
<div style="width:30px">
<i style="margin:8px" class="fa fa-users"/>
</div>
<div style="margin-left: 20px;width: 250px">
<div style="display:flex;justify-content:space-between;">
<span t-esc="data.name"/>
<t t-if="data.count > 0">
<span style="background-color:#ffeb3b;color:black;border-radius:50%;
width:18px;height:18px;font-size:10px;text-align:center;
line-height:18px;" t-esc="data.count"/>
</t>
</div>
<small style="color:#9c9a97; white-space: pre-line;" t-raw="data.message_body"/>
</div>
</t>
</t>
</div>
</div>`
</div>
</t>
</t>
</div>
</div>`;

69
pos_chatter/static/src/js/pos_systray_icon.js

@ -1,23 +1,68 @@
/** @odoo-module **/
import { Component } from "@odoo/owl";
import { Component, useRef, onWillUnmount, useState } from "@odoo/owl";
import { mount } from "@odoo/owl";
import { Navbar } from "@point_of_sale/app/navbar/navbar";
import { patch } from "@web/core/utils/patch";
import { PosMsgView } from "./pos_msg_view"
import { useRef} from "@odoo/owl";
import { PosMsgView } from "./pos_msg_view";
import { jsonrpc } from "@web/core/network/rpc_service";
patch(Navbar.prototype, {
setup() {
super.setup();
this.message = useRef('root')
},
onClick(ev) {
if($(".pos_systray_template").length == 0){
this.schedule_dropdown = mount(PosMsgView, document.body)
}else if($(".pos_systray_template").length > 0){
this.schedule_dropdown.then(function(res){
res.__owl__.remove()
})
this.message = useRef('root');
this.state = useState({
messageCount: 0,
});
this.schedule_dropdown = null;
this.refreshInterval = null;
// Initial fetch
this.fetchMessageCount();
// Set up interval for refreshing every 5 seconds
this.setupRefreshInterval();
// Clean up interval on component destruction
onWillUnmount(() => {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
}
});
},
setupRefreshInterval() {
// Refresh every 5 seconds (5000 milliseconds)
this.refreshInterval = setInterval(() => {
this.fetchMessageCount();
}, 5000);
},
fetchMessageCount() {
const self = this;
jsonrpc("/pos_systray/message_data").then((data) => {
const message_list = data.map((message) => ({
id: message.id,
type: message.type,
name: message.name,
message_body: new DOMParser().parseFromString(message.message_body, "text/html").documentElement.textContent,
count: message.count || 0,
}));
const totalCount = message_list.reduce((sum, msg) => sum + msg.count, 0);
self.state.messageCount = totalCount;
}).catch((error) => {
console.error("Failed to fetch message count:", error);
});
},
onClick() {
const systrayElements = document.querySelectorAll(".pos_systray_template");
if (systrayElements.length === 0) {
this.schedule_dropdown = mount(PosMsgView, document.body);
} else if (this.schedule_dropdown) {
this.schedule_dropdown.then((res) => {
res.__owl__.remove();
this.schedule_dropdown = null;
});
}
},
});

7
pos_chatter/static/src/xml/pos_systray_icon.xml

@ -6,8 +6,13 @@
<xpath expr="//div[hasclass('status-buttons')]" position="inside">
<div class="ticket-button" id="pos_systray_chatter_icon"
t-att-class="{ highlight: props.isTicketScreenShown }"
t-on-click="onClick">
t-on-click="onClick" style="cursor: pointer; position: relative;">
<i class="fa fa-comments"/>
<!-- Message count badge -->
<t t-if="state.messageCount > 0">
<span style="position: absolute; top: 4px; right: 7px; background: #ff0000; color: white; border-radius: 50%; width: 18px; height: 18px; font-size: 10px; text-align: center; line-height: 18px;"
t-esc="state.messageCount"/>
</t>
</div>
</xpath>
</t>

Loading…
Cancel
Save