feat(newwhats): ownership de sessões, auto-provisionamento, assinatura e drawer na agenda
- Drawer de WhatsApp focado (WhatsChatDrawer) aberto do slot do agendamento, com dropdown de números do paciente + grupo familiar e fluxo de iniciar conversa - Auto-provisionamento de conta no motor: eager no cadastro/convite + self-heal no proxy (404 "conta não encontrada" → provisiona e refaz o request) - Ownership por sessão (wa_session_authz): dono do workspace (owner_id ou vínculo donoclinica/donoconsultorio) tem poder total; delega re-scan, excluir sessão, apagar conversa/mensagem, acesso ao Inbox e à Secretária — por conta - Enforcement no proxy: guardSessionAction (scan/rescan/delete sessão), guardAreaAccess (inbox/secretaria), guardInboxDestructive (apagar conversa/msg) - Endpoints /api/nw/authz (dono gerencia) e /api/nw/access (acesso agregado) - Assinatura do operador nas mensagens (*Nome*, desambiguação de homônimos → *Rui C.*) - UI: painel "Gerenciar acesso" com 5 toggles; menu e botões de apagar condicionados - Proxy de mídia /api/nw/media + re-download sob demanda Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import { ptBR } from 'date-fns/locale';
|
||||
import { normalizeJid, parseSafeDate } from '../utils/inboxUtils';
|
||||
import { Message } from '../types/inboxTypes';
|
||||
import { mediaApi } from '../services/chatApiService';
|
||||
import { useChatStore } from '../store/chatStore';
|
||||
|
||||
// Message interface removed and moved to types.ts
|
||||
|
||||
@@ -31,6 +32,7 @@ interface MessageAreaProps {
|
||||
onEdit?: (msg: Message) => void;
|
||||
quickReactions: string[];
|
||||
getGroupSenderLabel: (msg: Message) => string;
|
||||
canDelete?: boolean;
|
||||
}
|
||||
|
||||
const MessageArea: React.FC<MessageAreaProps> = ({
|
||||
@@ -51,6 +53,7 @@ const MessageArea: React.FC<MessageAreaProps> = ({
|
||||
onEdit,
|
||||
quickReactions,
|
||||
getGroupSenderLabel,
|
||||
canDelete = true,
|
||||
}) => {
|
||||
const [showScrollBottom, setShowScrollBottom] = useState(false);
|
||||
const lastScrollTop = useRef(0);
|
||||
@@ -60,12 +63,17 @@ const MessageArea: React.FC<MessageAreaProps> = ({
|
||||
const [viewerMsgId, setViewerMsgId] = useState<string | null>(null)
|
||||
|
||||
// ── Re-download de mídia não baixada ─────────────────────────────────────
|
||||
// ~95% das mídias vêm com mediaUrl NULL (lazy). O motor re-baixa do WhatsApp
|
||||
// e devolve { mediaUrl }; atualizamos o store direto pelo retorno (o WS
|
||||
// message:media_ready não trafega pela ponte ext do satélite).
|
||||
const updateMediaReady = useChatStore((s) => s.updateMediaReady)
|
||||
const handleRedownloadMedia = useCallback(async (msg: Message) => {
|
||||
// selectedChat no formato legado usa instance_name (não instanceId)
|
||||
const instanceId = selectedChat?.instance_name ?? selectedChat?.instanceId
|
||||
if (!instanceId || !msg.id) return
|
||||
await mediaApi.redownload(instanceId, String(msg.id))
|
||||
}, [selectedChat?.instance_name, selectedChat?.instanceId])
|
||||
const { mediaUrl } = await mediaApi.redownload(instanceId, String(msg.id))
|
||||
if (mediaUrl) updateMediaReady(String(msg.id), mediaUrl)
|
||||
}, [selectedChat?.instance_name, selectedChat?.instanceId, updateMediaReady])
|
||||
const mediaMessages = messages.filter(m => m.type === 'image' || m.type === 'video')
|
||||
|
||||
const handleInternalScroll = useCallback(() => {
|
||||
@@ -204,6 +212,7 @@ const MessageArea: React.FC<MessageAreaProps> = ({
|
||||
onReact={onReact}
|
||||
onForward={onForward}
|
||||
onDelete={onDelete}
|
||||
canDelete={canDelete}
|
||||
onStar={onStar}
|
||||
onEdit={onEdit}
|
||||
quickReactions={quickReactions}
|
||||
|
||||
Reference in New Issue
Block a user