feat: Major WhatsApp integration update with Odoo and pause/resume
## Frontend - Add media display (images, audio, video, docs) in Inbox - Add pause/resume functionality for WhatsApp accounts - Fix media URLs to use nginx proxy (relative URLs) ## API Gateway - Add /accounts/:id/pause and /accounts/:id/resume endpoints - Fix media URL handling for browser access ## WhatsApp Core - Add pauseSession() - disconnect without logout - Add resumeSession() - reconnect using saved credentials - Add media download and storage for incoming messages - Serve media files via /media/ static route ## Odoo Module (odoo_whatsapp_hub) - Add Chat Hub interface with DOLLARS theme (dark, 3-column layout) - Add WhatsApp/DRRR theme switcher for chat view - Add "ABRIR CHAT" button in conversation form - Add send_message_from_chat() method - Add security/ir.model.access.csv - Fix CSS scoping to avoid breaking Odoo UI - Update webhook to handle message events properly ## Documentation - Add docs/CONTEXTO_DESARROLLO.md with complete project context ## Infrastructure - Add whatsapp_media Docker volume - Configure nginx proxy for /media/ route - Update .gitignore to track src/sessions/ source files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,7 @@ interface Message {
|
||||
direction: 'inbound' | 'outbound';
|
||||
type: string;
|
||||
content: string | null;
|
||||
media_url: string | null;
|
||||
created_at: string;
|
||||
is_internal_note: boolean;
|
||||
sent_by: string | null;
|
||||
@@ -368,6 +369,63 @@ export default function Inbox(): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
function renderMessageContent(msg: Message): JSX.Element {
|
||||
// Render media based on type
|
||||
if (msg.media_url) {
|
||||
const mediaType = msg.type?.toUpperCase();
|
||||
|
||||
if (mediaType === 'IMAGE') {
|
||||
return (
|
||||
<>
|
||||
<img
|
||||
src={msg.media_url}
|
||||
alt="Imagen"
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: 300,
|
||||
borderRadius: 4,
|
||||
marginBottom: msg.content ? 8 : 0,
|
||||
}}
|
||||
onClick={() => window.open(msg.media_url!, '_blank')}
|
||||
/>
|
||||
{msg.content && msg.content !== '[Image]' && (
|
||||
<Text style={{ color: 'inherit', display: 'block' }}>{msg.content}</Text>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (mediaType === 'AUDIO') {
|
||||
return (
|
||||
<audio controls style={{ maxWidth: '100%' }}>
|
||||
<source src={msg.media_url} type="audio/ogg" />
|
||||
Tu navegador no soporta audio.
|
||||
</audio>
|
||||
);
|
||||
}
|
||||
|
||||
if (mediaType === 'VIDEO') {
|
||||
return (
|
||||
<video controls style={{ maxWidth: '100%', maxHeight: 300, borderRadius: 4 }}>
|
||||
<source src={msg.media_url} type="video/mp4" />
|
||||
Tu navegador no soporta video.
|
||||
</video>
|
||||
);
|
||||
}
|
||||
|
||||
if (mediaType === 'DOCUMENT') {
|
||||
return (
|
||||
<a href={msg.media_url} target="_blank" rel="noopener noreferrer" style={{ color: 'inherit' }}>
|
||||
📄 {msg.content || 'Documento'}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Default text content
|
||||
return <Text style={{ color: 'inherit' }}>{msg.content}</Text>;
|
||||
}
|
||||
|
||||
function renderMessage(msg: Message): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
@@ -391,7 +449,7 @@ export default function Inbox(): JSX.Element {
|
||||
<FileTextOutlined /> Nota interna
|
||||
</Text>
|
||||
)}
|
||||
<Text style={{ color: 'inherit' }}>{msg.content}</Text>
|
||||
{renderMessageContent(msg)}
|
||||
</div>
|
||||
<Text
|
||||
type="secondary"
|
||||
|
||||
Reference in New Issue
Block a user