3042ddca38
- /wa-inbox, /wa-sessions e /wa-secretaria rodam sem a sidebar do scoreodonto (isStandaloneView); cada tela tem seu "Voltar" (Secretária ganhou botão na ThinNav). - SessionsView: banner "Você já possui acesso ao WhatsApp" quando há sessão ativa. - avatares: usa a URL do CDN (contactAvatarUrl/instance.avatar) direto; Avatar exibe sem depender de version; self-heal no onError re-busca a foto atual. - deps: framer-motion, immer. Dev override com HMR (docker-compose.dev.yml). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
160 lines
6.9 KiB
TypeScript
160 lines
6.9 KiB
TypeScript
'use client';
|
|
|
|
import React from 'react';
|
|
import {
|
|
ArrowLeft,
|
|
Search,
|
|
MoreVertical,
|
|
RefreshCw,
|
|
Tag,
|
|
X,
|
|
ClipboardList,
|
|
} from 'lucide-react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import {
|
|
formatPhone,
|
|
} from '../utils/inboxUtils';
|
|
import Avatar from './ui/Avatar';
|
|
|
|
interface InboxHeaderProps {
|
|
selectedChat: any;
|
|
isMobile: boolean;
|
|
isTyping: boolean;
|
|
isChatMenuOpen: boolean;
|
|
isSyncingAvatar: boolean;
|
|
isProtocolOpen?: boolean;
|
|
hasActiveProtocol?: boolean;
|
|
onBack: () => void;
|
|
onSearchClick: () => void;
|
|
onToggleMenu: () => void;
|
|
onSyncAvatar: () => void;
|
|
onAvatarClick?: () => void;
|
|
onProtocolClick?: () => void;
|
|
}
|
|
|
|
const InboxHeader: React.FC<InboxHeaderProps> = ({
|
|
selectedChat,
|
|
isMobile,
|
|
isTyping,
|
|
isChatMenuOpen,
|
|
isSyncingAvatar,
|
|
isProtocolOpen,
|
|
hasActiveProtocol,
|
|
onBack,
|
|
onSearchClick,
|
|
onToggleMenu,
|
|
onSyncAvatar,
|
|
onAvatarClick,
|
|
onProtocolClick,
|
|
}) => {
|
|
return (
|
|
<header className="h-[60px] px-4 bg-[#f0f2f5] flex items-center justify-between border-b border-[#d1d7db] z-30 relative shrink-0">
|
|
<div className="flex items-center gap-3 flex-1 min-w-0">
|
|
{isMobile && (
|
|
<button
|
|
onClick={onBack}
|
|
className="p-1 -ml-1 mr-1 hover:bg-black/5 rounded-full transition-colors active:scale-90"
|
|
>
|
|
<ArrowLeft className="w-6 h-6 text-[#54656f]" />
|
|
</button>
|
|
)}
|
|
|
|
{/* Identity Area - Clickable */}
|
|
<div
|
|
onClick={onAvatarClick}
|
|
className="flex items-center gap-3 min-w-0 cursor-pointer group/header hover:bg-black/5 px-2 py-1 -ml-2 rounded-md transition-all active:scale-[0.98]"
|
|
>
|
|
{/* Avatar */}
|
|
<Avatar chat={selectedChat} size={40} className="flex-shrink-0" />
|
|
|
|
<div className="flex flex-col min-w-0">
|
|
<h3 className="text-[16px] leading-[21px] font-bold text-[#111b21] flex items-center gap-2 truncate group-hover/header:text-[#00a884] transition-colors">
|
|
{selectedChat.lead_name || selectedChat.subject || formatPhone(selectedChat.phone, selectedChat.remote_jid)}
|
|
</h3>
|
|
<div className="flex items-center gap-1 min-w-0">
|
|
{isTyping ? (
|
|
<span className="text-[13px] text-[#00a884] font-medium animate-pulse">
|
|
digitando...
|
|
</span>
|
|
) : (
|
|
<span className="text-[12.5px] text-[#667781] truncate max-w-[300px] font-normal">
|
|
{selectedChat.bio || (
|
|
selectedChat.remote_jid?.endsWith('@g.us')
|
|
? `Clique para dados do grupo`
|
|
: 'Clique para informações do contato'
|
|
)}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-1">
|
|
{onProtocolClick && (
|
|
<button
|
|
onClick={onProtocolClick}
|
|
className={`p-2 rounded-full transition-all relative ${isProtocolOpen ? 'bg-black/10 text-[#111b21]' : 'hover:bg-black/5 text-[#54656f] hover:text-[#111b21]'}`}
|
|
title="Protocolos de atendimento"
|
|
>
|
|
<ClipboardList className="w-5 h-5" />
|
|
{hasActiveProtocol && !isProtocolOpen && (
|
|
<span className="absolute top-1.5 right-1.5 w-2 h-2 rounded-full bg-brand-500 border border-white" />
|
|
)}
|
|
</button>
|
|
)}
|
|
<button
|
|
onClick={onSearchClick}
|
|
className="p-2 hover:bg-black/5 rounded-full transition-all text-[#54656f] hover:text-[#111b21]"
|
|
title="Procurar na conversa"
|
|
>
|
|
<Search className="w-5 h-5" />
|
|
</button>
|
|
|
|
<div className="relative">
|
|
<button
|
|
onClick={onToggleMenu}
|
|
className={`p-2 rounded-full transition-all ${isChatMenuOpen ? 'bg-black/10 text-[#111b21]' : 'hover:bg-black/5 text-[#54656f]'}`}
|
|
>
|
|
<MoreVertical className="w-5 h-5" />
|
|
</button>
|
|
|
|
<AnimatePresence>
|
|
{isChatMenuOpen && (
|
|
<motion.div
|
|
initial={{ opacity: 0, scale: 0.95, y: 10 }}
|
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
exit={{ opacity: 0, scale: 0.95, y: 10 }}
|
|
className="absolute right-0 top-12 w-56 bg-white rounded-lg shadow-2xl border border-gray-100 py-2 z-50 overflow-hidden"
|
|
>
|
|
<button
|
|
onClick={() => {
|
|
onSyncAvatar();
|
|
onToggleMenu();
|
|
}}
|
|
disabled={isSyncingAvatar}
|
|
className="w-full flex items-center gap-3 px-4 py-2.5 text-[14px] text-[#111b21] hover:bg-[#f5f6f6] transition-colors disabled:opacity-50"
|
|
>
|
|
<RefreshCw className={`w-4 h-4 ${isSyncingAvatar ? 'animate-spin' : ''}`} />
|
|
Sincronizar Foto
|
|
</button>
|
|
<button className="w-full flex items-center gap-3 px-4 py-2.5 text-[14px] text-[#111b21] hover:bg-[#f5f6f6] transition-colors">
|
|
<Tag className="w-4 h-4" />
|
|
Gerenciar etiquetas
|
|
</button>
|
|
<div className="border-t border-gray-100 my-1"></div>
|
|
<button className="w-full flex items-center gap-3 px-4 py-2.5 text-[14px] font-bold text-red-600 hover:bg-red-50 transition-colors">
|
|
<X className="w-4 h-4" />
|
|
Limpar conversa
|
|
</button>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
);
|
|
};
|
|
|
|
export default InboxHeader;
|