import React, { useState, useEffect } from 'react';
import {
Menu, X, Copy, CheckCircle, MessageCircle,
Clock, ShieldAlert, ListChecks, Heart,
Users, ChevronRight, Upload, Save, Play, History,
Calendar, MapPin, Mail, Phone as PhoneIcon, UserCog,
Lock, LogOut, User
} from 'lucide-react';
// --- Componentes UI Reutilizáveis ---
const Button = ({ children, onClick, variant = 'primary', className = '', disabled = false, type = 'button' }) => {
const baseStyle = "px-4 py-2 rounded-lg transition-all duration-200 font-medium text-sm flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed";
const variants = {
primary: "bg-stone-800 text-white hover:bg-stone-700 shadow-sm",
secondary: "bg-stone-200 text-stone-800 hover:bg-stone-300",
outline: "border border-stone-300 text-stone-600 hover:bg-stone-50",
ghost: "text-stone-500 hover:text-stone-800 hover:bg-stone-100",
success: "bg-green-600 text-white hover:bg-green-700",
danger: "bg-red-50 text-red-600 hover:bg-red-100"
};
return (
{children}
);
};
const Card = ({ title, children, className = '' }) => (
{title && (
{title}
)}
{children}
);
// --- Tela de Login ---
const LoginScreen = ({ onLogin }) => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = (e) => {
e.preventDefault();
setError('');
setLoading(true);
// Simulação de delay de rede e verificação
setTimeout(() => {
// Base de usuários mockada
const users = {
'admin': { name: 'Adamo', role: 'Gerente' },
'comercial': { name: 'Angela', role: 'Consultor' },
'vendas': { name: 'Beatriz', role: 'SDR' }
};
if (users[username] && password === 'aosiQU33aosiQU33') {
onLogin({ username, ...users[username] });
} else {
setError('Credenciais inválidas. Tente admin / aosiQU33aosiQU33');
setLoading(false);
}
}, 800);
};
return (
PINHEIRO MARTINEZ
Acesso Restrito
);
};
// --- ScriptBox Inteligente (Conectado ao Lead e Atendente) ---
const ScriptBox = ({ title, content, context, onAction, activeLead, attendantName }) => {
const [copied, setCopied] = useState(false);
// Lógica de Primeiro Nome do Cliente
const clientFirstName = activeLead ? activeLead.name.split(' ')[0] : '[Nome]';
// Lógica do Nome do Atendente (usa o configurado ou mantém o placeholder se vazio)
const myName = attendantName || '[Seu Nome]';
// Personaliza o conteúdo substituindo placeholders
const processedContent = content
.replace(/\[Nome\]/g, clientFirstName)
.replace(/\[Nome do Cliente\]/g, clientFirstName)
.replace(/\[Seu Nome\]/g, myName);
const handleCopy = () => {
const textArea = document.createElement("textarea");
textArea.value = processedContent;
textArea.style.position = "fixed";
textArea.style.left = "-9999px";
textArea.style.top = "0";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
if (successful) {
setCopied(true);
if (activeLead && onAction) {
onAction(title, processedContent);
}
setTimeout(() => setCopied(false), 2000);
} else {
console.error('Falha ao copiar texto.');
}
} catch (err) {
console.error('Erro ao copiar texto: ', err);
}
document.body.removeChild(textArea);
};
return (
{title}
{activeLead && copied && (
Registrado para {clientFirstName}
)}
{context && {context} }
"{processedContent}"
{copied ? : }
);
};
// --- Seções de Conteúdo ---
const ManifestoSection = () => (
Postura e Tom de Voz
"Não vendemos plantas ou obras. Vendemos a materialização da identidade do cliente.
Nosso atendimento é o primeiro esboço do projeto: deve ser seguro, elegante, atento e sem pressa."
Consultores
Orientamos o cliente com autoridade.
Calmos
O luxo não tem pressa.
Ouvintes
O cliente fala mais que nós.
);
const FlowSection = ({ activeLead, logAction, attendantName }) => {
const [step, setStep] = useState(1);
return (
{activeLead && (
Atendendo Agora
{activeLead.name}
{activeLead.state && {activeLead.state} }
{activeLead.contactType && {activeLead.contactType} }
Status Atual
{activeLead.status}
)}
{[1, 2, 3, 4].map((s) => (
setStep(s)}
className={`flex items-center gap-2 px-4 py-2 rounded-full whitespace-nowrap transition-colors ${
step === s ? 'bg-stone-800 text-white' : 'bg-stone-100 text-stone-500 hover:bg-stone-200'
}`}
>
{s}
Etapa {s}
))}
{step === 1 && (
Objetivo: Mostrar que existe um humano do outro lado.
)}
{step === 2 && (
Opção A: Cliente aceitou ÁUDIO
Opção B: Cliente prefere TEXTO
)}
{step === 3 && (
)}
{step === 4 && (
)}
);
};
const LeadsManager = ({ leads, setLeads, onSelectLead, activeLead, attendantName, setAttendantName, currentUser }) => {
const [showImport, setShowImport] = useState(false);
const [importText, setImportText] = useState('');
const handleImport = () => {
const rows = importText.trim().split('\n');
const newLeads = rows.map((row, index) => {
const cols = row.split(',').map(s => s?.trim());
if (cols.length < 2) return null;
const [date, name, phone, email, state, type] = cols;
if (!name) return null;
return {
id: Date.now() + index,
date: date || new Date().toLocaleDateString(),
name: name,
phone: phone || 'Sem telefone',
email: email || '',
state: state || '',
contactType: type || 'Lead',
status: 'Novo',
history: [],
lastInteraction: new Date().toISOString()
};
}).filter(Boolean);
setLeads([...leads, ...newLeads]);
setImportText('');
setShowImport(false);
};
return (
{/* Configuração do Atendente */}
Logado como
{attendantName}
{currentUser?.role || 'Usuário'}
{/* Cabeçalho e Botões */}
Gestão de Leads
Importe e controle seus contatos.
setShowImport(!showImport)} variant="outline">
Importar CSV
{showImport && (
Formato obrigatório das colunas (separado por vírgula):
Data de contato, Nome, Telefone, Email, Estado, Tipo de Contato
Exemplo:
22/11/2023, Ana Souza, 11999999999, ana@email.com, SP, Indicação
23/11/2023, Carlos Lima, 11888888888, carlos@email.com, RJ, Instagram
)}
Data
Nome
Contato
Detalhes
Status
Ação
{leads.length === 0 ? (
Nenhum lead importado ainda.
) : (
leads.map(lead => (
{lead.date || '-'}
{lead.name}
{lead.phone}
{lead.contactType}
{lead.phone &&
{lead.phone} }
{lead.email &&
{lead.email} }
{lead.state && {lead.state} }
{lead.contactType && {lead.contactType} }
{lead.status}
{activeLead?.id === lead.id ? (
Ativo
) : (
onSelectLead(lead)} variant="secondary" className="ml-auto text-xs py-1 px-3">
Atender
)}
))
)}
);
};
const ObjectionsSection = ({ attendantName, activeLead, logAction }) => {
const objections = [
{
trigger: "Só estou pesquisando / Ainda não tenho as chaves",
response: "Compreendo perfeitamente, [Nome]. Essa fase de pesquisa é fundamental para amadurecer as ideias. Vou deixar seu contato salvo aqui. Posso te enviar ocasionalmente algumas referências de projetos nossos que tenham a ver com esse estilo que você busca? Assim você vai se inspirando até o momento certo."
},
{
trigger: "Qual é o preço/valor do metro quadrado?",
response: "A nossa precificação é personalizada, [Nome], pois varia conforme a complexidade e o nível de detalhamento que o seu projeto vai exigir. Não trabalhamos com valor fixo de m² justamente para não limitar a criação. Na reunião com o arquiteto, ele consegue entender a dimensão do trabalho e te apresentar uma proposta formal. Faz sentido para você conversarmos para ele entender o escopo?"
},
{
trigger: "Preciso ver com meu marido/esposa",
response: "Claro! É fundamental que ambos estejam alinhados, afinal a casa é a realização da família. Vamos fazer o seguinte: qual o melhor horário para vocês dois? Assim apresentamos o escritório para ambos e tiramos todas as dúvidas juntos."
}
];
const disqualifications = [
{
trigger: "Projeto Comercial (Fora de nicho)",
response: "Entendi sua demanda, [Nome]. Hoje, a Pinheiro Martinez tomou a decisão estratégica de focar 100% na arquitetura residencial de alto padrão. Acreditamos que a especialização é o segredo da excelência, e projetos comerciais exigem dinâmicas e normativas muito específicas que fugiriam do nosso foco de entrega atual. Por isso, infelizmente não conseguirei te atender nesta demanda, mas agradeço muito por ter lembrado de nós!"
},
{
trigger: "Projeto Pequeno / Apenas Decoração",
response: "Obrigada por compartilhar, [Nome]. Para garantirmos a assinatura e a coesão que você vê no nosso portfólio, o escritório optou por trabalhar exclusivamente com projetos completos ou grandes reformas. Quando atuamos em ambientes isolados, sentimos que não conseguimos entregar a experiência de transformação completa que é a marca da Pinheiro Martinez. De qualquer forma, fico à disposição caso decida ampliar o escopo no futuro!"
}
];
return (
{objections.map((obj, idx) => (
{obj.trigger}
))}
{disqualifications.map((obj, idx) => (
{obj.trigger}
))}
);
};
const CadenceSection = ({ attendantName, activeLead, logAction }) => (
Dia 1
Primeiro contato (Roteiros do Fluxo).
Dia 3 - Check de Intenção
);
const ChecklistSection = () => {
const [checks, setChecks] = useState({ nome: false, tipo: false, local: false, estagio: false, dor: false });
const toggleCheck = (key) => setChecks(prev => ({ ...prev, [key]: !prev[key] }));
return (
Este checklist deve ser preenchido ANTES de passar o bastão para o Arquiteto.
{[
{ key: 'nome', label: 'Nome do cliente e decisores (Quem assina o cheque?)' },
{ key: 'tipo', label: 'Tipo de obra (Construção do zero ou Reforma completa)' },
{ key: 'local', label: 'Localização (Bairro/Condomínio/Cidade e especificidades)' },
{ key: 'estagio', label: 'Estágio (Já tem terreno/chaves ou está sonhando?)' },
{ key: 'dor', label: 'Principal dor ou desejo (Ex: "Quer privacidade", "Ama cozinhar")' }
].map((item) => (
toggleCheck(item.key)}
className={`flex items-center p-4 rounded-lg border cursor-pointer transition-all ${checks[item.key] ? 'bg-stone-800 border-stone-800 text-white' : 'bg-white border-stone-200 text-stone-600 hover:bg-stone-50'}`}>
{checks[item.key] && }
{item.label}
))}
);
};
// --- Layout Principal ---
export default function PlaybookApp() {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [currentUser, setCurrentUser] = useState(null);
const [activeTab, setActiveTab] = useState('leads');
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
// Estado Global dos Leads e Atendente
const [leads, setLeads] = useState(() => {
const saved = localStorage.getItem('pm_leads_v2');
return saved ? JSON.parse(saved) : [
{
id: 1,
date: '20/11/2023',
name: 'Cliente Exemplo',
phone: '11999999999',
email: 'cliente@exemplo.com',
state: 'SP',
contactType: 'Instagram',
status: 'Novo',
history: []
}
];
});
const [attendantName, setAttendantName] = useState('');
// Persistência de Sessão
useEffect(() => {
const savedUser = localStorage.getItem('pm_user_session');
if (savedUser) {
const user = JSON.parse(savedUser);
setCurrentUser(user);
setAttendantName(user.name);
setIsAuthenticated(true);
}
}, []);
const handleLogin = (user) => {
setIsAuthenticated(true);
setCurrentUser(user);
setAttendantName(user.name); // Auto-preenche o nome nos scripts
localStorage.setItem('pm_user_session', JSON.stringify(user));
};
const handleLogout = () => {
setIsAuthenticated(false);
setCurrentUser(null);
setAttendantName('');
localStorage.removeItem('pm_user_session');
};
const [activeLead, setActiveLead] = useState(null);
useEffect(() => {
localStorage.setItem('pm_leads_v2', JSON.stringify(leads));
}, [leads]);
const logLeadAction = (actionTitle, contentSent) => {
if (!activeLead) return;
const newHistoryItem = {
date: new Date().toISOString(),
action: actionTitle,
content: contentSent,
user: currentUser?.name // Loga quem fez a ação
};
const updatedLeads = leads.map(l => {
if (l.id === activeLead.id) {
return {
...l,
status: 'Em Contato',
lastInteraction: new Date().toISOString(),
history: [...l.history, newHistoryItem]
};
}
return l;
});
setLeads(updatedLeads);
setActiveLead(updatedLeads.find(l => l.id === activeLead.id));
};
const handleSelectLead = (lead) => {
setActiveLead(lead);
setActiveTab('flow');
};
const menuItems = [
{ id: 'leads', label: 'Gestão de Leads', icon: Users },
{ id: 'manifesto', label: 'Manifesto', icon: Heart },
{ id: 'flow', label: 'Fluxo de Atendimento', icon: MessageCircle },
{ id: 'objections', label: 'Objeções', icon: ShieldAlert },
{ id: 'cadence', label: 'Cadência', icon: Clock },
{ id: 'checklist', label: 'Checklist', icon: ListChecks },
];
const renderContent = () => {
switch (activeTab) {
case 'leads': return ;
case 'manifesto': return ;
case 'flow': return ;
case 'objections': return ;
case 'cadence': return ;
case 'checklist': return ;
default: return ;
}
};
// Se não estiver logado, mostra tela de Login
if (!isAuthenticated) {
return ;
}
// App Principal
return (
{/* Sidebar Desktop */}
{/* Mobile Header */}
setIsMobileMenuOpen(!isMobileMenuOpen)}>
{isMobileMenuOpen ? : }
{/* Mobile Menu Overlay */}
{isMobileMenuOpen && (
{menuItems.map(item => (
{
setActiveTab(item.id);
setIsMobileMenuOpen(false);
}}
className={`w-full flex items-center gap-3 px-4 py-4 text-sm rounded-lg transition-colors border border-stone-800 ${
activeTab === item.id
? 'bg-stone-800 text-white'
: 'text-stone-400 hover:bg-stone-800'
}`}
>
{item.label}
))}
Sair
)}
{/* Main Content */}
);
}