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>
174 lines
8.8 KiB
TypeScript
174 lines
8.8 KiB
TypeScript
'use client';
|
|
|
|
import React, { useMemo } from 'react';
|
|
import {
|
|
X,
|
|
ChevronRight,
|
|
Image as ImageIcon,
|
|
Video as VideoIcon,
|
|
FileText,
|
|
Mic,
|
|
Bell,
|
|
Star,
|
|
Clock,
|
|
Ban,
|
|
ThumbsDown,
|
|
Trash2
|
|
} from 'lucide-react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import {
|
|
formatPhone,
|
|
getMediaUrl
|
|
} from '../utils/inboxUtils';
|
|
import Avatar from './ui/Avatar';
|
|
import { Message } from '../types/inboxTypes';
|
|
|
|
interface ContactProfileProps {
|
|
isOpen: boolean;
|
|
chat: any;
|
|
messages: Message[];
|
|
onClose: () => void;
|
|
}
|
|
|
|
const ContactProfile: React.FC<ContactProfileProps> = ({
|
|
isOpen,
|
|
chat,
|
|
messages,
|
|
onClose,
|
|
}) => {
|
|
// Filter media messages
|
|
const mediaMessages = useMemo(() => {
|
|
return messages.filter(m => m.type === 'image' || m.type === 'video' || m.type === 'document' || m.type === 'audio');
|
|
}, [messages]);
|
|
|
|
const photoVideos = mediaMessages.filter(m => m.type === 'image' || m.type === 'video');
|
|
const documents = mediaMessages.filter(m => m.type === 'document');
|
|
const audios = mediaMessages.filter(m => m.type === 'audio');
|
|
|
|
if (!chat) return null;
|
|
|
|
return (
|
|
<AnimatePresence>
|
|
{isOpen && (
|
|
<motion.div
|
|
initial={{ x: '100%' }}
|
|
animate={{ x: 0 }}
|
|
exit={{ x: '100%' }}
|
|
transition={{ type: 'spring', damping: 25, stiffness: 200 }}
|
|
className="w-[400px] h-full bg-[#f0f2f5] border-l border-[#d1d7db] flex flex-col z-40 relative flex-shrink-0"
|
|
>
|
|
{/* Header */}
|
|
<div className="h-[60px] px-4 bg-white flex items-center gap-6 border-b border-[#d1d7db] shrink-0">
|
|
<button
|
|
onClick={onClose}
|
|
className="p-2 hover:bg-black/5 rounded-full transition-colors"
|
|
>
|
|
<X className="w-6 h-6 text-[#54656f]" />
|
|
</button>
|
|
<h2 className="text-[16px] font-normal text-[#111b21]">Dados do contato</h2>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="flex-1 overflow-y-auto overflow-x-hidden custom-scrollbar pb-10">
|
|
{/* Profile Section */}
|
|
<div className="bg-white px-8 py-8 flex flex-col items-center mb-2 shadow-sm">
|
|
{/* Profile Photo */}
|
|
<Avatar chat={chat} size={200} className="mb-5 shadow-lg" />
|
|
<h1 className="text-[24px] font-normal text-[#111b21] mb-1 text-center">
|
|
{chat.lead_name || chat.subject || formatPhone(chat.phone, chat.remote_jid)}
|
|
</h1>
|
|
<p className="text-[16px] text-[#667781] font-normal mb-1">
|
|
{formatPhone(chat.phone, chat.remote_jid)}
|
|
</p>
|
|
{chat.bio && (
|
|
<p className="text-[14px] text-[#667781] font-normal mt-2 text-center max-w-xs">
|
|
{chat.bio}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Status (Bio) */}
|
|
<div className="bg-white px-8 py-4 mb-2 shadow-sm">
|
|
<h3 className="text-[14px] text-[#667781] font-normal mb-3">Recado</h3>
|
|
<p className="text-[16px] text-[#111b21] font-normal leading-relaxed">
|
|
{chat.bio || 'Sem recado disponível'}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Media Section */}
|
|
<div className="bg-white mb-2 shadow-sm">
|
|
<button className="w-full px-8 py-4 flex items-center justify-between hover:bg-[#f5f6f6] transition-colors group">
|
|
<span className="text-[14px] text-[#667781] font-normal">Mídia, links e documentos</span>
|
|
<div className="flex items-center gap-1">
|
|
<span className="text-[14px] text-[#667781] font-normal">{mediaMessages.length}</span>
|
|
<ChevronRight className="w-5 h-5 text-[#8696a0] group-hover:translate-x-0.5 transition-transform" />
|
|
</div>
|
|
</button>
|
|
|
|
{mediaMessages.length > 0 ? (
|
|
<div className="px-8 pb-4 grid grid-cols-3 gap-1">
|
|
{photoVideos.slice(0, 3).map((m, i) => (
|
|
<div
|
|
key={m.id || i}
|
|
className="aspect-square bg-gray-100 rounded-sm overflow-hidden cursor-pointer hover:opacity-90 transition-opacity"
|
|
onClick={() => {
|
|
const url = getMediaUrl(m.media_url);
|
|
if (url) window.open(url, '_blank');
|
|
}}
|
|
>
|
|
<div className="w-full h-full flex items-center justify-center bg-black/10">
|
|
{m.type === 'video' ? <VideoIcon className="w-6 h-6 text-white" /> : <ImageIcon className="w-6 h-6 text-gray-400" />}
|
|
</div>
|
|
</div>
|
|
))}
|
|
{mediaMessages.length > 3 && (
|
|
<div className="aspect-square flex items-center justify-center bg-gray-50 border border-dashed border-gray-200 text-gray-400 text-xs rounded-sm">
|
|
+{mediaMessages.length - 3}
|
|
</div>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<div className="px-8 pb-6 flex flex-col items-center justify-center text-center">
|
|
<p className="text-[14px] text-[#667781] italic">Nenhuma mídia compartilhada</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Actions Section */}
|
|
<div className="bg-white mb-2 shadow-sm py-2">
|
|
{[
|
|
{ icon: Bell, label: 'Silenciar notificações', color: '#111b21' },
|
|
{ icon: Star, label: 'Mensagens favoritas', color: '#111b21' },
|
|
{ icon: Clock, label: 'Mensagens temporárias', sub: 'Desativadas', color: '#111b21' },
|
|
].map((item, idx) => (
|
|
<button key={idx} className="w-full px-8 py-3.5 flex items-center gap-6 hover:bg-[#f5f6f6] transition-colors text-left">
|
|
<item.icon className="w-5 h-5 text-[#8696a0]" />
|
|
<div className="flex flex-col">
|
|
<span className="text-[16px] text-[#111b21] font-normal leading-none">{item.label}</span>
|
|
{item.sub && <span className="text-[12px] text-[#667781] mt-1">{item.sub}</span>}
|
|
</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
<div className="bg-white mb-2 shadow-sm py-2">
|
|
{[
|
|
{ icon: Ban, label: `Bloquear ${chat.lead_name || 'Contato'}`, color: '#ea0038' },
|
|
{ icon: ThumbsDown, label: `Denunciar ${chat.lead_name || 'Contato'}`, color: '#ea0038' },
|
|
{ icon: Trash2, label: 'Apagar conversa', color: '#ea0038' }
|
|
].map((item, idx) => (
|
|
<button key={idx} className="w-full px-8 py-3.5 flex items-center gap-6 hover:bg-[#f5f6f6] transition-colors text-left">
|
|
<item.icon className="w-5 h-5" style={{ color: item.color }} />
|
|
<span className="text-[16px] font-normal" style={{ color: item.color }}>{item.label}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
};
|
|
|
|
export default ContactProfile;
|