Files
scoreodonto.com/frontend/views/newwhats/components/ChatSidebar.tsx
T
VPS 4 Builder 21bebd3256 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>
2026-07-02 23:26:31 +02:00

111 lines
4.2 KiB
TypeScript

'use client';
import React, { useMemo, useState } from 'react';
import type { NewWhatsChat, PresenceState } from '../types/inboxTypes';
import ChatList from './ChatList';
interface ChatSidebarProps {
chats: NewWhatsChat[];
loading: boolean;
selectedChat: NewWhatsChat | null;
searchTerm: string;
selectedLabelFilter: string | null;
activeFilter: string;
chatScope: 'inbox' | 'favorites' | 'archived' | 'custom_list';
activeCustomListName?: string | null;
presenceByJid: Record<string, PresenceState>;
isFavoriteChat: (chat: NewWhatsChat) => boolean;
onSelectChat: (chat: NewWhatsChat) => void;
onMenuAction: (action: any, chat: NewWhatsChat) => void;
drafts?: Record<string, string>;
canDelete?: boolean;
}
export default function ChatSidebar({
chats,
loading,
selectedChat,
searchTerm,
selectedLabelFilter,
activeFilter,
chatScope,
activeCustomListName,
presenceByJid,
isFavoriteChat,
onSelectChat,
onMenuAction,
drafts = {},
canDelete = true,
}: ChatSidebarProps) {
// UI Pills (Simplified, for now we keep layout consistent)
const [pillFilter, setPillFilter] = useState<'all' | 'unread' | 'favorites' | 'groups'>('all');
const filteredChats = useMemo(() => chats.filter((chat) => {
const isGroup = chat.remote_jid.endsWith('@g.us');
// Filter by pill
if (pillFilter === 'unread' && chat.unread_count === 0) return false;
if (pillFilter === 'groups' && !isGroup) return false;
if (pillFilter === 'favorites' && !isFavoriteChat(chat)) return false;
const displayName = chat.displayName || chat.subject || chat.phone || chat.remote_jid;
const matchesSearch = displayName.toLowerCase().includes(searchTerm.toLowerCase()) ||
(chat.last_message_text || '').toLowerCase().includes(searchTerm.toLowerCase());
const matchesLabel = !selectedLabelFilter ||
(chat.labels && chat.labels.some((l) => l.label_id === selectedLabelFilter));
return matchesSearch && matchesLabel;
}), [chats, searchTerm, selectedLabelFilter, pillFilter, isFavoriteChat]);
const sortedFilteredChats = useMemo(
() => [...filteredChats].sort((a, b) => {
const aPinned = Boolean(a.is_pinned);
const bPinned = Boolean(b.is_pinned);
if (aPinned !== bPinned) return aPinned ? -1 : 1;
const tA = a.last_message_time ? new Date(a.last_message_time).getTime() : 0;
const tB = b.last_message_time ? new Date(b.last_message_time).getTime() : 0;
return tB - tA;
}),
[filteredChats]
);
return (
<div className="flex flex-col h-full bg-white border-r border-[#e9edef] overflow-hidden">
{/* Filter Pills */}
<div className="px-3 py-2 flex items-center gap-2 overflow-x-auto no-scrollbar flex-shrink-0 border-b border-[#f0f2f5]">
{(['all', 'unread', 'favorites', 'groups'] as const).map((filter) => (
<button
key={filter}
onClick={() => setPillFilter(filter)}
className={`px-3 py-1 rounded-full text-sm whitespace-nowrap transition-colors ${
pillFilter === filter
? 'bg-[#00a884] text-white'
: 'bg-[#f0f2f5] text-[#54656f] hover:bg-[#e9edef]'
}`}
>
{filter === 'all' && 'Tudo'}
{filter === 'unread' && 'Não lidas'}
{filter === 'favorites' && 'Favoritos'}
{filter === 'groups' && 'Grupos'}
</button>
))}
</div>
{/* Scrollable Chat List */}
<ChatList
chats={sortedFilteredChats}
loading={loading}
selectedChat={selectedChat}
onSelectChat={onSelectChat}
onMenuAction={onMenuAction}
presenceByJid={presenceByJid}
isFavoriteChat={isFavoriteChat}
drafts={drafts}
canDelete={canDelete}
/>
</div>
);
}