666 lines
32 KiB
TypeScript
666 lines
32 KiB
TypeScript
import React, { useState, useEffect, useRef } from 'react';
|
||
import { Mail, MessageSquare, BookOpen, Send, User, Reply, CheckCircle2, AlertCircle, Smartphone, Save, Smile, Megaphone, Clock } from 'lucide-react';
|
||
import CustomModal from './CustomModal';
|
||
|
||
interface SupportTicket {
|
||
id: string;
|
||
content: string;
|
||
status: string;
|
||
createdAt: string;
|
||
user: {
|
||
id: string;
|
||
name: string;
|
||
email: string;
|
||
avatarUrl: string | null;
|
||
whatsapp: string | null;
|
||
};
|
||
}
|
||
|
||
interface Note {
|
||
id: string;
|
||
content: string;
|
||
createdAt: string;
|
||
user: {
|
||
id: string;
|
||
name: string;
|
||
email: string;
|
||
avatarUrl: string | null;
|
||
whatsapp: string | null;
|
||
};
|
||
lesson: {
|
||
title: string;
|
||
module: {
|
||
course: {
|
||
title: string;
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
// Simple emoji picker groups
|
||
const EMOJI_LIST = [
|
||
'😊','😁','🎉','🙏','💪','🚀','❤️','✅','⭐','🔥','💡','📚','🎓','👏','🤝',
|
||
'💰','💳','🛒','📱','📧','🌟','🏆','✨','🎯','📢','👋','😀','🥳','💎','🌈'
|
||
];
|
||
|
||
// Default template texts
|
||
const DEFAULT_TEMPLATES: Record<string, string> = {
|
||
whatsappTemplatePayment: `Olá, *{nome}*! 🎉
|
||
|
||
Recebemos a confirmação do seu pagamento com sucesso!
|
||
|
||
💰 *Valor:* {valor}
|
||
📦 *Plano/Curso:* {plano}{curso}
|
||
|
||
Seu acesso já está liberado na plataforma. Qualquer dúvida, estamos à disposição.
|
||
|
||
Muito obrigado pela sua confiança! 🙏
|
||
_Equipe MicrotecFlix_`,
|
||
|
||
whatsappTemplateWelcome: `Olá, *{nome}*! 👋
|
||
|
||
Seja muito bem-vindo(a) à *MicrotecFlix*! 🎓🚀
|
||
|
||
Estamos felizes em ter você conosco. Agora você tem acesso à nossa plataforma de cursos de informática e pacote Office.
|
||
|
||
Acesse: https://estudo.microtecinformaticacurso.com.br
|
||
|
||
Se tiver qualquer dúvida, é só chamar. 😊
|
||
_Equipe MicrotecFlix_`,
|
||
|
||
whatsappTemplateNewContent: `Oi, *{nome}*! 🎬
|
||
|
||
Temos uma novidade imperdível para você na plataforma!
|
||
|
||
⭐ *Novo conteúdo disponível:* Acabamos de publicar um novo curso/módulo/aula.
|
||
|
||
Acesse agora mesmo e aproveite: https://estudo.microtecinformaticacurso.com.br
|
||
|
||
Bons estudos! 💪
|
||
_Equipe MicrotecFlix_`,
|
||
|
||
whatsappTemplatePromo: `Oi, *{nome}*! 🌟
|
||
|
||
Temos uma oferta especial para você hoje!
|
||
|
||
🎯 Aproveite nossa promoção exclusiva e acelere seus estudos de informática e pacote Office.
|
||
|
||
Não perca essa oportunidade única! Acesse agora:
|
||
https://estudo.microtecinformaticacurso.com.br
|
||
|
||
Qualquer dúvida, estamos aqui. 🙏
|
||
_Equipe MicrotecFlix_`
|
||
};
|
||
|
||
export default function AdminMessagesView() {
|
||
const [activeTab, setActiveTab] = useState<'tickets' | 'notes' | 'whatsapp_templates'>('tickets');
|
||
const [tickets, setTickets] = useState<SupportTicket[]>([]);
|
||
const [notes, setNotes] = useState<Note[]>([]);
|
||
const [isLoading, setIsLoading] = useState(true);
|
||
const [replyText, setReplyText] = useState('');
|
||
const [replyingTo, setReplyingTo] = useState<string | null>(null);
|
||
const [isSending, setIsSending] = useState(false);
|
||
const [alertModal, setAlertModal] = useState<{isOpen: boolean, title: string, message: string, isError?: boolean}>({isOpen: false, title: '', message: ''});
|
||
|
||
// WhatsApp Templates State
|
||
const [settings, setSettings] = useState<any>(null);
|
||
const [activeTemplateModal, setActiveTemplateModal] = useState<string | null>(null);
|
||
const [currentTemplateText, setCurrentTemplateText] = useState('');
|
||
const [isSavingTemplate, setIsSavingTemplate] = useState(false);
|
||
const [isTestingTemplate, setIsTestingTemplate] = useState<string | null>(null);
|
||
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||
|
||
// Broadcast state
|
||
const [broadcastText, setBroadcastText] = useState('');
|
||
const [broadcastDelay, setBroadcastDelay] = useState(10);
|
||
const [isBroadcasting, setIsBroadcasting] = useState(false);
|
||
const [showBroadcastEmojiPicker, setShowBroadcastEmojiPicker] = useState(false);
|
||
const broadcastRef = useRef<HTMLTextAreaElement>(null);
|
||
|
||
useEffect(() => {
|
||
fetchData();
|
||
}, []);
|
||
|
||
const fetchData = async () => {
|
||
setIsLoading(true);
|
||
try {
|
||
const token = localStorage.getItem('admin_token');
|
||
const [msgRes, settingsRes] = await Promise.all([
|
||
fetch('/api/admin/messages', { headers: { 'Authorization': `Bearer ${token}` } }),
|
||
fetch('/api/admin/settings', { headers: { 'Authorization': `Bearer ${token}` } })
|
||
]);
|
||
if (msgRes.ok) {
|
||
const data = await msgRes.json();
|
||
setTickets(data.tickets);
|
||
setNotes(data.notes);
|
||
}
|
||
if (settingsRes.ok) {
|
||
const data = await settingsRes.json();
|
||
setSettings(data);
|
||
// Pre-fill broadcast from template
|
||
if (data.whatsappTemplatePromo) {
|
||
setBroadcastText(data.whatsappTemplatePromo);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('Error fetching data:', error);
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
};
|
||
|
||
const handleReply = async (userId: string, ticketId?: string) => {
|
||
if (!replyText.trim()) return;
|
||
setIsSending(true);
|
||
try {
|
||
const token = localStorage.getItem('admin_token');
|
||
const res = await fetch('/api/admin/reply-message', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${token}`,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({ userId, message: replyText, ticketId })
|
||
});
|
||
if (res.ok) {
|
||
setReplyText('');
|
||
setReplyingTo(null);
|
||
fetchData();
|
||
setAlertModal({isOpen: true, title: 'Sucesso', message: 'Resposta enviada com sucesso!'});
|
||
} else {
|
||
setAlertModal({isOpen: true, title: 'Erro', message: 'Erro ao enviar resposta.', isError: true});
|
||
}
|
||
} catch (error) {
|
||
console.error('Error replying:', error);
|
||
setAlertModal({isOpen: true, title: 'Erro', message: 'Erro interno ao enviar resposta.', isError: true});
|
||
} finally {
|
||
setIsSending(false);
|
||
}
|
||
};
|
||
|
||
const handleSaveTemplate = async (field: string) => {
|
||
setIsSavingTemplate(true);
|
||
try {
|
||
const token = localStorage.getItem('admin_token');
|
||
const res = await fetch('/api/admin/settings', {
|
||
method: 'POST',
|
||
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ [field]: currentTemplateText })
|
||
});
|
||
if (res.ok) {
|
||
setSettings({...settings, [field]: currentTemplateText});
|
||
setActiveTemplateModal(null);
|
||
setAlertModal({isOpen: true, title: 'Sucesso', message: 'Template salvo com sucesso! O envio automático já está ativado.'});
|
||
} else {
|
||
setAlertModal({isOpen: true, title: 'Erro', message: 'Erro ao salvar template.', isError: true});
|
||
}
|
||
} catch (error) {
|
||
console.error('Error saving template:', error);
|
||
setAlertModal({isOpen: true, title: 'Erro', message: 'Erro interno ao salvar template.', isError: true});
|
||
} finally {
|
||
setIsSavingTemplate(false);
|
||
}
|
||
};
|
||
|
||
const handleTestTemplate = async (templateId: string, e: React.MouseEvent) => {
|
||
e.stopPropagation();
|
||
setIsTestingTemplate(templateId);
|
||
try {
|
||
const token = localStorage.getItem('admin_token');
|
||
const res = await fetch('/api/admin/whatsapp/test-template', {
|
||
method: 'POST',
|
||
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ templateId })
|
||
});
|
||
const data = await res.json();
|
||
if (res.ok) {
|
||
setAlertModal({isOpen: true, title: 'Enviado! 📱', message: data.message});
|
||
} else {
|
||
setAlertModal({isOpen: true, title: 'Erro no Teste', message: data.error, isError: true});
|
||
}
|
||
} catch (error) {
|
||
console.error('Error testing template:', error);
|
||
setAlertModal({isOpen: true, title: 'Erro', message: 'Erro interno ao testar template.', isError: true});
|
||
} finally {
|
||
setIsTestingTemplate(null);
|
||
}
|
||
};
|
||
|
||
const insertEmoji = (emoji: string, isForBroadcast = false) => {
|
||
const ref = isForBroadcast ? broadcastRef : textareaRef;
|
||
const setter = isForBroadcast ? setBroadcastText : setCurrentTemplateText;
|
||
const current = isForBroadcast ? broadcastText : currentTemplateText;
|
||
if (ref.current) {
|
||
const start = ref.current.selectionStart;
|
||
const end = ref.current.selectionEnd;
|
||
const newText = current.substring(0, start) + emoji + current.substring(end);
|
||
setter(newText);
|
||
setTimeout(() => {
|
||
if (ref.current) {
|
||
ref.current.focus();
|
||
ref.current.setSelectionRange(start + emoji.length, start + emoji.length);
|
||
}
|
||
}, 0);
|
||
} else {
|
||
setter(current + emoji);
|
||
}
|
||
if (isForBroadcast) setShowBroadcastEmojiPicker(false);
|
||
else setShowEmojiPicker(false);
|
||
};
|
||
|
||
const handleBroadcast = async () => {
|
||
if (!broadcastText.trim()) return;
|
||
setIsBroadcasting(true);
|
||
try {
|
||
const token = localStorage.getItem('admin_token');
|
||
const res = await fetch('/api/admin/whatsapp/broadcast', {
|
||
method: 'POST',
|
||
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ message: broadcastText, delaySeconds: broadcastDelay })
|
||
});
|
||
const data = await res.json();
|
||
if (res.ok) {
|
||
setAlertModal({isOpen: true, title: 'Envio Iniciado! 🚀', message: data.message || 'Broadcast iniciado com sucesso!'});
|
||
} else {
|
||
setAlertModal({isOpen: true, title: 'Erro', message: data.error || 'Erro ao iniciar broadcast.', isError: true});
|
||
}
|
||
} catch (error) {
|
||
console.error('Error broadcasting:', error);
|
||
setAlertModal({isOpen: true, title: 'Erro', message: 'Erro interno ao enviar.', isError: true});
|
||
} finally {
|
||
setIsBroadcasting(false);
|
||
}
|
||
};
|
||
|
||
const whatsappTemplates = [
|
||
{ id: 'whatsappTemplatePayment', title: 'Pagamento Confirmado', desc: 'Enviada automaticamente quando o aluno paga um plano ou curso.', icon: '💰', tags: ['{nome}', '{valor}', '{plano}', '{curso}'] },
|
||
{ id: 'whatsappTemplateWelcome', title: 'Boas-Vindas (Nova Conta)', desc: 'Enviada automaticamente quando o aluno cria uma conta.', icon: '👋', tags: ['{nome}'] },
|
||
{ id: 'whatsappTemplateNewContent', title: 'Novo Conteúdo (Carrossel)', desc: 'Enviada manualmente ao postar curso/módulo/aula.', icon: '🎬', tags: ['{nome}'] },
|
||
];
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
|
||
<div>
|
||
<h2 className="text-2xl font-bold text-white flex items-center space-x-2">
|
||
<Mail className="w-6 h-6 text-[#E50914]" />
|
||
<span>Mensagens e Comunicações</span>
|
||
</h2>
|
||
<p className="text-zinc-400 text-sm mt-1">Gerencie dúvidas, anotações e configure as mensagens automáticas do WhatsApp.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex space-x-4 border-b border-white/10 overflow-x-auto">
|
||
<button
|
||
onClick={() => setActiveTab('tickets')}
|
||
className={`pb-3 px-1 text-sm font-bold flex items-center space-x-2 border-b-2 transition-colors whitespace-nowrap ${activeTab === 'tickets' ? 'border-[#E50914] text-white' : 'border-transparent text-zinc-500 hover:text-zinc-300'}`}
|
||
>
|
||
<MessageSquare className="w-4 h-4" />
|
||
<span>Comentários / Ajuda ({tickets.length})</span>
|
||
</button>
|
||
<button
|
||
onClick={() => setActiveTab('notes')}
|
||
className={`pb-3 px-1 text-sm font-bold flex items-center space-x-2 border-b-2 transition-colors whitespace-nowrap ${activeTab === 'notes' ? 'border-[#E50914] text-white' : 'border-transparent text-zinc-500 hover:text-zinc-300'}`}
|
||
>
|
||
<BookOpen className="w-4 h-4" />
|
||
<span>Anotações de Aulas ({notes.length})</span>
|
||
</button>
|
||
<button
|
||
onClick={() => setActiveTab('whatsapp_templates')}
|
||
className={`pb-3 px-1 text-sm font-bold flex items-center space-x-2 border-b-2 transition-colors whitespace-nowrap ${activeTab === 'whatsapp_templates' ? 'border-[#E50914] text-white' : 'border-transparent text-zinc-500 hover:text-zinc-300'}`}
|
||
>
|
||
<Smartphone className="w-4 h-4" />
|
||
<span>Modelos WhatsApp</span>
|
||
</button>
|
||
</div>
|
||
|
||
{isLoading ? (
|
||
<div className="flex justify-center items-center py-20">
|
||
<div className="w-8 h-8 border-4 border-[#E50914] border-t-transparent rounded-full animate-spin"></div>
|
||
</div>
|
||
) : (
|
||
<div className="space-y-4">
|
||
{activeTab === 'tickets' && (
|
||
tickets.length === 0 ? (
|
||
<div className="text-center py-12 text-zinc-500">Nenhum comentário recebido.</div>
|
||
) : (
|
||
tickets.map(ticket => (
|
||
<div key={ticket.id} className="bg-zinc-900 border border-white/10 rounded-xl p-5 space-y-4 relative">
|
||
{ticket.status === 'REPLIED' && (
|
||
<div className="absolute top-4 right-4 text-[10px] uppercase font-bold text-emerald-500 bg-emerald-500/10 px-2 py-1 rounded">
|
||
Respondido
|
||
</div>
|
||
)}
|
||
<div className="flex items-start space-x-4">
|
||
<div className="w-10 h-10 rounded-full bg-zinc-800 flex items-center justify-center flex-shrink-0 overflow-hidden">
|
||
{ticket.user.avatarUrl ? (
|
||
<img src={ticket.user.avatarUrl} alt={ticket.user.name} className="w-full h-full object-cover" />
|
||
) : (
|
||
<User className="w-5 h-5 text-zinc-400" />
|
||
)}
|
||
</div>
|
||
<div>
|
||
<h4 className="font-bold text-white text-sm">{ticket.user.name}</h4>
|
||
<p className="text-xs text-zinc-500">{ticket.user.email} {ticket.user.whatsapp && `• WA: ${ticket.user.whatsapp}`}</p>
|
||
<p className="text-[10px] text-zinc-600 mt-0.5">{new Date(ticket.createdAt).toLocaleString('pt-BR')}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-black/20 p-4 rounded-lg text-zinc-300 text-sm whitespace-pre-wrap">
|
||
{ticket.content}
|
||
</div>
|
||
|
||
{replyingTo === ticket.id ? (
|
||
<div className="mt-4 space-y-3">
|
||
<textarea
|
||
value={replyText}
|
||
onChange={(e) => setReplyText(e.target.value)}
|
||
placeholder="Escreva sua resposta (será enviada via Notificação e WhatsApp se ativado)..."
|
||
className="w-full h-24 bg-black/40 border border-white/10 rounded-lg p-3 text-white text-sm focus:outline-none focus:border-[#E50914] resize-none"
|
||
/>
|
||
<div className="flex justify-end space-x-2">
|
||
<button onClick={() => { setReplyingTo(null); setReplyText(''); }} className="px-3 py-1.5 text-xs text-zinc-400 hover:text-white transition-colors">Cancelar</button>
|
||
<button
|
||
onClick={() => handleReply(ticket.user.id, ticket.id)}
|
||
disabled={!replyText.trim() || isSending}
|
||
className="bg-[#E50914] hover:bg-red-700 text-white px-4 py-1.5 rounded text-xs font-bold transition-colors disabled:opacity-50 flex items-center space-x-2"
|
||
>
|
||
{isSending ? <div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /> : <><Send className="w-3 h-3" /><span>Enviar Resposta</span></>}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<button onClick={() => { setReplyingTo(ticket.id); setReplyText(''); }} className="text-xs text-[#E50914] hover:text-red-400 transition-colors font-bold flex items-center space-x-1">
|
||
<Reply className="w-3 h-3" />
|
||
<span>Responder</span>
|
||
</button>
|
||
)}
|
||
</div>
|
||
))
|
||
)
|
||
)}
|
||
|
||
{activeTab === 'notes' && (
|
||
notes.length === 0 ? (
|
||
<div className="text-center py-12 text-zinc-500">Nenhuma anotação registrada.</div>
|
||
) : (
|
||
notes.map(note => (
|
||
<div key={note.id} className="bg-zinc-900 border border-white/10 rounded-xl p-5 space-y-4">
|
||
<div className="flex items-start justify-between">
|
||
<div className="flex items-start space-x-4">
|
||
<div className="w-10 h-10 rounded-full bg-zinc-800 flex items-center justify-center flex-shrink-0 overflow-hidden">
|
||
{note.user.avatarUrl ? (
|
||
<img src={note.user.avatarUrl} alt={note.user.name} className="w-full h-full object-cover" />
|
||
) : (
|
||
<User className="w-5 h-5 text-zinc-400" />
|
||
)}
|
||
</div>
|
||
<div>
|
||
<h4 className="font-bold text-white text-sm">{note.user.name}</h4>
|
||
<p className="text-xs text-zinc-500">{note.user.email}</p>
|
||
<p className="text-[10px] text-zinc-600 mt-0.5">{new Date(note.createdAt).toLocaleString('pt-BR')}</p>
|
||
</div>
|
||
</div>
|
||
<div className="text-right max-w-[200px]">
|
||
<span className="text-[10px] uppercase font-bold text-blue-500 bg-blue-500/10 px-2 py-1 rounded block truncate">
|
||
{note.lesson.module.course.title}
|
||
</span>
|
||
<span className="text-xs text-zinc-400 block mt-1 truncate">{note.lesson.title}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-black/20 p-4 rounded-lg text-zinc-300 text-sm whitespace-pre-wrap">
|
||
{note.content}
|
||
</div>
|
||
|
||
{replyingTo === note.id ? (
|
||
<div className="mt-4 space-y-3">
|
||
<textarea
|
||
value={replyText}
|
||
onChange={(e) => setReplyText(e.target.value)}
|
||
placeholder="Escreva sua resposta para esta anotação..."
|
||
className="w-full h-24 bg-black/40 border border-white/10 rounded-lg p-3 text-white text-sm focus:outline-none focus:border-[#E50914] resize-none"
|
||
/>
|
||
<div className="flex justify-end space-x-2">
|
||
<button onClick={() => { setReplyingTo(null); setReplyText(''); }} className="px-3 py-1.5 text-xs text-zinc-400 hover:text-white transition-colors">Cancelar</button>
|
||
<button
|
||
onClick={() => handleReply(note.user.id)}
|
||
disabled={!replyText.trim() || isSending}
|
||
className="bg-[#E50914] hover:bg-red-700 text-white px-4 py-1.5 rounded text-xs font-bold transition-colors disabled:opacity-50 flex items-center space-x-2"
|
||
>
|
||
{isSending ? <div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /> : <><Send className="w-3 h-3" /><span>Enviar Resposta</span></>}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<button onClick={() => { setReplyingTo(note.id); setReplyText(''); }} className="text-xs text-[#E50914] hover:text-red-400 transition-colors font-bold flex items-center space-x-1">
|
||
<Reply className="w-3 h-3" />
|
||
<span>Responder</span>
|
||
</button>
|
||
)}
|
||
</div>
|
||
))
|
||
)
|
||
)}
|
||
|
||
{activeTab === 'whatsapp_templates' && (
|
||
<div className="space-y-6 mt-4">
|
||
{/* Automatic Templates */}
|
||
<div>
|
||
<h3 className="text-sm font-bold text-zinc-400 uppercase tracking-wider mb-3 flex items-center gap-2">
|
||
<Smartphone className="w-4 h-4 text-green-500" />
|
||
Mensagens Automáticas
|
||
</h3>
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
{whatsappTemplates.map(t => (
|
||
<div
|
||
key={t.id}
|
||
className="bg-zinc-900 border border-white/10 rounded-xl p-5 hover:border-white/25 transition-all cursor-pointer group"
|
||
onClick={() => {
|
||
setCurrentTemplateText(settings?.[t.id] || DEFAULT_TEMPLATES[t.id] || '');
|
||
setActiveTemplateModal(t.id);
|
||
}}
|
||
>
|
||
<div className="flex items-start gap-3 mb-3">
|
||
<span className="text-2xl">{t.icon}</span>
|
||
<div className="flex-1 min-w-0">
|
||
<h3 className="text-sm font-bold text-white group-hover:text-[#E50914] transition-colors">{t.title}</h3>
|
||
<p className="text-[11px] text-zinc-500 leading-snug mt-0.5">{t.desc}</p>
|
||
</div>
|
||
</div>
|
||
<div className="bg-black/30 p-2.5 rounded-lg text-[11px] text-zinc-400 line-clamp-3 min-h-[50px] whitespace-pre-wrap mb-2">
|
||
{settings?.[t.id] || DEFAULT_TEMPLATES[t.id] || 'Clique para configurar...'}
|
||
</div>
|
||
<div className="flex flex-wrap gap-1">
|
||
{t.tags.map(tag => (
|
||
<span key={tag} className="text-[9px] bg-blue-500/10 text-blue-300 px-1.5 py-0.5 rounded font-mono">{tag}</span>
|
||
))}
|
||
</div>
|
||
<div className="mt-3 flex items-center justify-between">
|
||
<div className="flex items-center gap-1.5">
|
||
<div className={`w-2 h-2 rounded-full ${settings?.[t.id] ? 'bg-green-500' : 'bg-zinc-600'}`}></div>
|
||
<span className={`text-[10px] font-bold ${settings?.[t.id] ? 'text-green-400' : 'text-zinc-500'}`}>
|
||
{settings?.[t.id] ? 'Ativo' : 'Não configurado'}
|
||
</span>
|
||
</div>
|
||
|
||
{settings?.[t.id] && (
|
||
<button
|
||
onClick={(e) => handleTestTemplate(t.id, e)}
|
||
disabled={isTestingTemplate === t.id}
|
||
className="bg-zinc-800 hover:bg-zinc-700 text-zinc-300 px-3 py-1 rounded text-[10px] font-bold flex items-center gap-1 transition-colors"
|
||
>
|
||
{isTestingTemplate === t.id ? (
|
||
<div className="w-3 h-3 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
||
) : (
|
||
<>
|
||
<Send className="w-3 h-3" />
|
||
<span>Testar Envio</span>
|
||
</>
|
||
)}
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Mass Broadcast */}
|
||
<div className="bg-zinc-900 border border-amber-500/20 rounded-xl p-6">
|
||
<div className="flex items-center gap-3 mb-4">
|
||
<div className="w-10 h-10 bg-amber-500/10 rounded-full flex items-center justify-center text-xl">🚀</div>
|
||
<div>
|
||
<h3 className="text-lg font-bold text-white flex items-center gap-2">
|
||
<Megaphone className="w-5 h-5 text-amber-400" />
|
||
Promoção em Massa
|
||
</h3>
|
||
<p className="text-xs text-zinc-500">Envia para todos os alunos que têm WhatsApp cadastrado.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-amber-500/5 border border-amber-500/15 p-3 rounded-lg text-xs text-amber-200 mb-4 space-y-1">
|
||
<p><strong>⚠️ Anti-Spam:</strong> Configure o intervalo de envio para evitar bloqueio pelo WhatsApp.</p>
|
||
<p><strong>Tag dinâmica:</strong> Use <code className="bg-black/40 px-1 rounded">{'{nome}'}</code> para personalizar com o primeiro nome do aluno.</p>
|
||
</div>
|
||
|
||
<div className="space-y-3">
|
||
<div className="relative">
|
||
<textarea
|
||
ref={broadcastRef}
|
||
value={broadcastText}
|
||
onChange={(e) => setBroadcastText(e.target.value)}
|
||
className="w-full h-40 bg-black/40 border border-white/10 rounded-lg p-3 pr-12 text-white text-sm focus:outline-none focus:border-amber-500/50 resize-none"
|
||
placeholder="Escreva a mensagem de promoção aqui..."
|
||
/>
|
||
<div className="absolute bottom-3 right-3">
|
||
<button
|
||
onClick={() => setShowBroadcastEmojiPicker(!showBroadcastEmojiPicker)}
|
||
className="w-8 h-8 flex items-center justify-center text-zinc-400 hover:text-amber-400 transition-colors"
|
||
title="Inserir emoji"
|
||
>
|
||
<Smile className="w-5 h-5" />
|
||
</button>
|
||
</div>
|
||
{showBroadcastEmojiPicker && (
|
||
<div className="absolute bottom-12 right-3 bg-zinc-800 border border-white/10 rounded-xl p-3 grid grid-cols-6 gap-1.5 z-10 shadow-2xl">
|
||
{EMOJI_LIST.map(emoji => (
|
||
<button key={emoji} onClick={() => insertEmoji(emoji, true)} className="text-xl hover:scale-125 transition-transform p-1 rounded hover:bg-white/10">
|
||
{emoji}
|
||
</button>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="flex items-center gap-3">
|
||
<Clock className="w-4 h-4 text-amber-400 flex-shrink-0" />
|
||
<label className="text-xs text-zinc-300 flex-shrink-0">Intervalo entre mensagens:</label>
|
||
<input
|
||
type="number"
|
||
value={broadcastDelay}
|
||
onChange={e => setBroadcastDelay(Math.max(5, Math.min(120, Number(e.target.value))))}
|
||
min={5}
|
||
max={120}
|
||
className="w-20 bg-black/40 border border-white/10 rounded-lg px-3 py-1.5 text-white text-sm focus:outline-none focus:border-amber-500/50"
|
||
/>
|
||
<span className="text-xs text-zinc-500">segundos (mín: 5s / máx: 120s)</span>
|
||
</div>
|
||
|
||
<div className="flex justify-end">
|
||
<button
|
||
onClick={handleBroadcast}
|
||
disabled={!broadcastText.trim() || isBroadcasting}
|
||
className="bg-amber-600 hover:bg-amber-500 text-white px-6 py-2.5 rounded-lg font-bold transition-colors disabled:opacity-50 flex items-center space-x-2"
|
||
>
|
||
{isBroadcasting
|
||
? <><div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /><span>Iniciando...</span></>
|
||
: <><Megaphone className="w-4 h-4" /><span>Enviar para Todos</span></>
|
||
}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* Template Edit Modal */}
|
||
<CustomModal isOpen={!!activeTemplateModal} onClose={() => { setActiveTemplateModal(null); setShowEmojiPicker(false); }} title={whatsappTemplates.find(t => t.id === activeTemplateModal)?.title || 'Editar Template'} isGlass={true}>
|
||
<div className="space-y-4">
|
||
<div className="bg-blue-500/10 border border-blue-500/20 p-3 rounded-lg text-xs text-blue-200 space-y-1">
|
||
<p><strong>Formatação do WhatsApp:</strong> Use <code className="bg-black/40 px-1 rounded">*negrito*</code>, <code className="bg-black/40 px-1 rounded">_itálico_</code>, <code className="bg-black/40 px-1 rounded">~riscado~</code>.</p>
|
||
<p><strong>Tags disponíveis:</strong>{' '}
|
||
{whatsappTemplates.find(t => t.id === activeTemplateModal)?.tags.map(tag => (
|
||
<code key={tag} className="bg-black/40 px-1 rounded font-bold text-blue-100 mr-1">{tag}</code>
|
||
))}
|
||
</p>
|
||
</div>
|
||
|
||
<div className="relative">
|
||
<textarea
|
||
ref={textareaRef}
|
||
value={currentTemplateText}
|
||
onChange={(e) => setCurrentTemplateText(e.target.value)}
|
||
className="w-full h-52 bg-black/40 border border-white/10 rounded-lg p-3 pr-12 text-white text-sm focus:outline-none focus:border-[#E50914] resize-none"
|
||
placeholder="Escreva a mensagem aqui..."
|
||
/>
|
||
<div className="absolute bottom-3 right-3">
|
||
<button
|
||
onClick={() => setShowEmojiPicker(!showEmojiPicker)}
|
||
className="w-8 h-8 flex items-center justify-center text-zinc-400 hover:text-[#E50914] transition-colors"
|
||
title="Inserir emoji"
|
||
>
|
||
<Smile className="w-5 h-5" />
|
||
</button>
|
||
</div>
|
||
{showEmojiPicker && (
|
||
<div className="absolute bottom-12 right-3 bg-zinc-800 border border-white/10 rounded-xl p-3 grid grid-cols-6 gap-1.5 z-10 shadow-2xl">
|
||
{EMOJI_LIST.map(emoji => (
|
||
<button key={emoji} onClick={() => insertEmoji(emoji, false)} className="text-xl hover:scale-125 transition-transform p-1 rounded hover:bg-white/10">
|
||
{emoji}
|
||
</button>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="flex justify-end space-x-2">
|
||
<button onClick={() => { setActiveTemplateModal(null); setShowEmojiPicker(false); }} className="px-4 py-2 text-zinc-400 hover:text-white transition-colors text-sm">
|
||
Cancelar
|
||
</button>
|
||
<button
|
||
onClick={() => handleSaveTemplate(activeTemplateModal!)}
|
||
disabled={isSavingTemplate}
|
||
className="bg-[#E50914] hover:bg-red-700 text-white px-6 py-2 rounded-lg font-bold transition-colors disabled:opacity-50 flex items-center space-x-2"
|
||
>
|
||
{isSavingTemplate ? <div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /> : <><Save className="w-4 h-4" /><span>Salvar Template</span></>}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</CustomModal>
|
||
|
||
{/* Alert Modal */}
|
||
<CustomModal isOpen={alertModal.isOpen} onClose={() => setAlertModal({...alertModal, isOpen: false})} title={alertModal.title} isGlass={true}>
|
||
<div className="flex items-start gap-4">
|
||
{alertModal.isError ? (
|
||
<div className="bg-red-500/10 p-3 rounded-full shrink-0"><AlertCircle className="w-6 h-6 text-red-500" /></div>
|
||
) : (
|
||
<div className="bg-green-500/10 p-3 rounded-full shrink-0"><CheckCircle2 className="w-6 h-6 text-green-500" /></div>
|
||
)}
|
||
<div className="flex-1">
|
||
<p className="text-zinc-300 text-base">{alertModal.message}</p>
|
||
<div className="mt-6 flex justify-end">
|
||
<button onClick={() => setAlertModal({...alertModal, isOpen: false})} className="px-6 py-2 bg-zinc-800 hover:bg-zinc-700 text-white rounded-lg font-bold transition-colors">
|
||
OK
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</CustomModal>
|
||
</div>
|
||
);
|
||
}
|