diff --git a/pos_chatter/__init__.py b/pos_chatter/__init__.py
index eb43f035a..d773dae75 100644
--- a/pos_chatter/__init__.py
+++ b/pos_chatter/__init__.py
@@ -19,3 +19,4 @@
# If not, see .
################################################################################
from . import controllers
+from . import models
diff --git a/pos_chatter/controllers/systray.py b/pos_chatter/controllers/systray.py
index f5adfb086..72a16a6b8 100644
--- a/pos_chatter/controllers/systray.py
+++ b/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
diff --git a/pos_chatter/models/__init__.py b/pos_chatter/models/__init__.py
new file mode 100644
index 000000000..a2bc21ba5
--- /dev/null
+++ b/pos_chatter/models/__init__.py
@@ -0,0 +1 @@
+from . import mail_message
diff --git a/pos_chatter/models/mail_message.py b/pos_chatter/models/mail_message.py
new file mode 100644
index 000000000..d4e51d2cc
--- /dev/null
+++ b/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)}")
+
+
+
+
diff --git a/pos_chatter/static/description/assets/screenshots/1.png b/pos_chatter/static/description/assets/screenshots/1.png
index a3b37c99f..68681b491 100644
Binary files a/pos_chatter/static/description/assets/screenshots/1.png and b/pos_chatter/static/description/assets/screenshots/1.png differ
diff --git a/pos_chatter/static/description/assets/screenshots/2.png b/pos_chatter/static/description/assets/screenshots/2.png
index c690610e7..f539bb126 100644
Binary files a/pos_chatter/static/description/assets/screenshots/2.png and b/pos_chatter/static/description/assets/screenshots/2.png differ
diff --git a/pos_chatter/static/description/index.html b/pos_chatter/static/description/index.html
index 808a8f536..23afb5bf4 100644
--- a/pos_chatter/static/description/index.html
+++ b/pos_chatter/static/description/index.html
@@ -161,7 +161,7 @@
- Chat list.
+ Chat list with unread message counts.
diff --git a/pos_chatter/static/src/js/pos_msg_view.js b/pos_chatter/static/src/js/pos_msg_view.js
index 0e3a9ce21..1bf33f410 100644
--- a/pos_chatter/static/src/js/pos_msg_view.js
+++ b/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
-
-
+
+PosMsgView.template = xml`
+
+
+
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+ margin-bottom: 15px;padding: 4px 12px;display:flex;cursor:pointer;position:relative;" t-att-value="data.id"
+ t-on-click="_onClickToMessage">
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
- `;
\ No newline at end of file
diff --git a/pos_chatter/static/src/js/pos_systray_icon.js b/pos_chatter/static/src/js/pos_systray_icon.js
index 8f2f46a76..25c24b968 100644
--- a/pos_chatter/static/src/js/pos_systray_icon.js
+++ b/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;
+ });
}
},
-});
+});
\ No newline at end of file
diff --git a/pos_chatter/static/src/xml/pos_systray_icon.xml b/pos_chatter/static/src/xml/pos_systray_icon.xml
index bae446cc8..f2cc06d66 100644
--- a/pos_chatter/static/src/xml/pos_systray_icon.xml
+++ b/pos_chatter/static/src/xml/pos_systray_icon.xml
@@ -6,9 +6,14 @@
+ t-on-click="onClick" style="cursor: pointer; position: relative;">
+
+
+
+
-
+
\ No newline at end of file