Files
scoreodonto.com/frontend/views/newwhats/components/ThinSidebar.tsx
T
VPS 4 Builder cfeac891fb feat(wa-inbox): botão liga/desliga da Secretária por sessão na thin sidebar
Botão Power na thin sidebar do /wa-inbox age SÓ na sessão/número aberto (verde=on,
vermelho pulsante=off). Abre modal com textarea de motivo (obrigatório p/ desligar),
histórico de quem ligou/desligou e aviso de rate-limit (1x/hora). secSessionPowerApi
+ SessaoSecretariaPower.tsx.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 19:39:41 +02:00

111 lines
5.1 KiB
TypeScript

import React from 'react';
import {
MessageCircle,
Megaphone,
Users,
Settings,
BrainCircuit,
} from 'lucide-react';
import { getAvatarProxyUrl } from '../utils/inboxUtils';
import { SessaoSecretariaPower } from './SessaoSecretariaPower';
export type TabType = 'chats' | 'broadcasts' | 'groups' | 'settings';
interface ThinSidebarProps {
activeTab: TabType;
setActiveTab: (tab: TabType) => void;
activeInstance: any; // We'll pass the active instance to show the avatar
onNavigate?: (view: string) => void; // satélite: navegação entre as views do plugin
}
export default function ThinSidebar({ activeTab, setActiveTab, activeInstance, onNavigate }: ThinSidebarProps) {
const isConnected = activeInstance?.status === 'connected' || activeInstance?.status === 'open';
const renderIcon = (id: TabType, IconComponent: any, title: string) => {
const isActive = activeTab === id;
return (
<button
key={id}
title={title}
onClick={() => {
if (id === 'groups') {
onNavigate?.('wa-sessions');
} else {
setActiveTab(id);
}
}}
className={`w-10 h-10 mb-2 rounded-full flex items-center justify-center transition-colors ${
isActive ? 'bg-[#d9dbdf]' : 'hover:bg-[#d9dbdf]/60'
}`}
>
<IconComponent
className={`w-6 h-6 ${isActive ? 'text-[#111b21]' : 'text-[#54656f]'}`}
fill={isActive ? 'currentColor' : 'none'}
/>
</button>
);
};
return (
<div className="w-[60px] h-full bg-[#f0f2f5] border-r border-[#d1d7db] flex flex-col items-center py-4 flex-shrink-0 z-30">
{/* Top Icons */}
<div className="flex-1 w-full flex flex-col items-center">
{renderIcon('chats', MessageCircle, 'Conversas')}
{renderIcon('broadcasts', Megaphone, 'Transmissões')}
{renderIcon('groups', Users, 'Sessões')}
{/* Secretária IA — abre a tela de configuração da secretária virtual */}
<button
title="Secretária IA"
onClick={() => onNavigate?.('wa-secretaria')}
className="w-10 h-10 mb-2 rounded-full flex items-center justify-center transition-colors hover:bg-[#d9dbdf]/60"
>
<BrainCircuit className="w-6 h-6 text-[#54656f]" />
</button>
</div>
{/* Bottom Icons */}
<div className="w-full flex flex-col items-center pb-2">
{/* Liga/desliga a Secretária IA APENAS desta sessão (número atual) */}
<SessaoSecretariaPower instanceId={activeInstance?.instance ?? null} />
{/* Configurações — abre o painel na área central (aba 'settings') */}
{renderIcon('settings', Settings, 'Configurações')}
<div className="relative mt-2 p-1 cursor-pointer">
<div className="w-8 h-8 rounded-full overflow-hidden bg-gray-200 border border-gray-300">
{(() => {
// Avatar da instância SEMPRE via proxy do backend. Nunca usar a URL
// crua do CDN do WhatsApp (pps.whatsapp.net) como fallback: ela expira
// e o navegador recebe 403 (hotlink). Sem proxy resolvível → ícone.
const avatarSrc = activeInstance?.instance
? getAvatarProxyUrl({
instance_name: activeInstance.instance,
remote_jid: activeInstance.phone ? `${activeInstance.phone}@s.whatsapp.net` : null,
})
: null;
return avatarSrc ? (
<img
src={avatarSrc}
alt="Status"
className="w-full h-full object-cover"
onError={(e) => { e.currentTarget.style.display = 'none'; }}
/>
) : (
<div className="w-full h-full flex items-center justify-center bg-[#dfe5e7] text-[#54656f]">
<Users className="w-5 h-5" />
</div>
);
})()}
</div>
{/* Status indicator badge */}
<div
className={`absolute bottom-0.5 right-0.5 w-3 h-3 rounded-full border-2 border-[#f0f2f5] ${
isConnected ? 'bg-emerald-500' : 'bg-red-500'
}`}
title={isConnected ? 'Conectado' : 'Desconectado'}
></div>
</div>
</div>
</div>
);
}