From 681c0a9173dec5dd01575c7c8ebe77015a132522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Consultor=C3=ADa=20Alcaraz=20Salazar?= Date: Fri, 28 Aug 2026 08:23:56 +0000 Subject: [PATCH] Chat interno 1-a-1 entre usuarios de la plataforma (addon skeen_chat + pagina Chat con badge de no leidos) --- frontend/src/App.tsx | 2 + frontend/src/components/ClasicoLayout.tsx | 15 +- frontend/src/components/HomeNestLayout.tsx | 19 ++ frontend/src/components/SkeenLayout.tsx | 11 +- frontend/src/lib/chat.ts | 27 ++ frontend/src/pages/Chat.tsx | 273 ++++++++++++++++++ frontend/src/services/odoo.ts | 42 +++ odoo-addons/skeen_chat/__init__.py | 4 + odoo-addons/skeen_chat/__manifest__.py | 21 ++ .../skeen_chat/controllers/__init__.py | 3 + odoo-addons/skeen_chat/controllers/chat.py | 165 +++++++++++ odoo-addons/skeen_chat/models/__init__.py | 3 + odoo-addons/skeen_chat/models/chat_message.py | 22 ++ .../skeen_chat/security/ir.model.access.csv | 2 + 14 files changed, 607 insertions(+), 2 deletions(-) create mode 100644 frontend/src/lib/chat.ts create mode 100644 frontend/src/pages/Chat.tsx create mode 100644 odoo-addons/skeen_chat/__init__.py create mode 100644 odoo-addons/skeen_chat/__manifest__.py create mode 100644 odoo-addons/skeen_chat/controllers/__init__.py create mode 100644 odoo-addons/skeen_chat/controllers/chat.py create mode 100644 odoo-addons/skeen_chat/models/__init__.py create mode 100644 odoo-addons/skeen_chat/models/chat_message.py create mode 100644 odoo-addons/skeen_chat/security/ir.model.access.csv diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0a3b9ef..bd4c6e4 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -22,6 +22,7 @@ import Cumpleanos from './pages/Cumpleanos'; import Inventario from './pages/Inventario'; import WacrmMessages from './pages/WacrmMessages'; import WacrmLeads from './pages/WacrmLeads'; +import Chat from './pages/Chat'; import Login from './pages/Login'; import './App.css'; @@ -81,6 +82,7 @@ const App: FC = () => { } /> } /> } /> + } /> } /> } /> } /> diff --git a/frontend/src/components/ClasicoLayout.tsx b/frontend/src/components/ClasicoLayout.tsx index 9ed9668..503bc2f 100644 --- a/frontend/src/components/ClasicoLayout.tsx +++ b/frontend/src/components/ClasicoLayout.tsx @@ -35,6 +35,7 @@ import GlobalSearch from './GlobalSearch'; import ThemePicker from './ThemePicker'; import NotificationBell from './NotificationBell'; import { useAuth, ROLE_LABELS } from '../lib/auth'; +import { useChatUnread } from '../lib/chat'; import { useQuickLinks } from '../lib/quicklinks'; import { odooApi } from '../services/odoo'; @@ -114,6 +115,7 @@ const menuTree: MenuEntry[] = [ { label: 'Comunicación', icon: MessageSquare, children: [ + { label: 'Chat', href: '/chat' }, { label: 'Cumpleañeros', href: '/cumpleanos' }, { label: 'Mensajes', href: '/wacrm/messages' }, ], @@ -143,6 +145,7 @@ export const ClasicoLayout: FC = ({ children, title: _title, subtit const navigate = useNavigate(); const { user, canSee, hasRole, logout } = useAuth(); const [sidebarOpen, setSidebarOpen] = useState(false); + const chatUnread = useChatUnread(); const visibleLeaves = useMemo( () => menuLeaves.filter((it) => canSee(it.href)), @@ -383,7 +386,12 @@ export const ClasicoLayout: FC = ({ children, title: _title, subtit > {entry.label} - + {entry.children?.some((c) => c.href === '/chat') && chatUnread > 0 && ( + + {chatUnread} + + )} + c.href === '/chat') && chatUnread > 0 ? '' : 'ml-auto'} transition-transform ${open ? '' : '-rotate-90'}`} /> {open && (
    @@ -405,6 +413,11 @@ export const ClasicoLayout: FC = ({ children, title: _title, subtit > {child.label} + {child.href === '/chat' && chatUnread > 0 && ( + + {chatUnread} + + )} + + ))} +
+ )} + + + + {/* Columna derecha: hilo */} +
+ {!activeUser ? ( +
+ +
+ ) : ( + <> +
+ + + {initials(activeUser.name)} + +
+

{activeUser.name}

+

{ROLE_LABELS[activeUser.role] || activeUser.role}

+
+
+ +
+ {loadingThread ? ( +

Cargando mensajes...

+ ) : messages.length === 0 ? ( +

+ Aún no hay mensajes. Escribe el primero. +

+ ) : ( + messages.map((m) => ( +
+
+

{m.body}

+

+ {fmtTime(m.date)} +

+
+
+ )) + )} +
+
+ +
+