PautaManager
`;const printWindow = window.open('', '_blank'); if(printWindow) { printWindow.document.write(htmlContent); printWindow.document.close(); } else { alert("Por favor, permita a abertura de pop-ups no seu navegador para gerar o PDF."); } setShowExportModal(false); };const days = []; for (let i = 0; i < firstDayIndex; i++) days.push(
);for (let d = 1; d <= daysInMonth; d++) { const dateStr = `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`; const dayPosts = (posts||[]).filter(p => { if (!p || !p.date) return false; return p.date.startsWith(dateStr); }); const isToday = new Date().toISOString().split('T')[0] === dateStr;days.push(
{d}
{dayPosts.filter(Boolean).map(post => { const platformInfo = POST_PLATFORMS[post.platform] || POST_PLATFORMS['other']; const statusInfo = POST_STATUS[post.status] || POST_STATUS['rascunho']; const client = (clients||[]).find(c => String(c?.id) === String(post.clientId)); return (
onPostClick(post)} className={`text-[10px] flex items-center px-1.5 py-1 rounded cursor-pointer hover:opacity-80 font-medium ${platformInfo.colorClass}`} title={`${client ? client.name + ' - ' : ''}${post.time || ''} - ${statusInfo.label}`} style={{ borderLeft: client ? `4px solid ${client.color}` : 'none' }}>
) })}
); }return (

Calendário de Postagens

{monthNames[currentDate.getMonth()]} {currentDate.getFullYear()}

Status:
{Object.values(POST_STATUS).map(s => (
{s.label}
))}
{['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'].map(day => (
{day}
))}
{days}
{showExportModal && (

Exportar Cronograma

setExportConfig({...exportConfig, startDate: e.target.value})} className="w-full px-3 py-3 bg-slate-50 dark:bg-neutral-950 border border-slate-300 dark:border-neutral-700 rounded-xl outline-none focus:ring-2 focus:ring-[#9DCD5A] text-slate-700 dark:text-neutral-200" />
setExportConfig({...exportConfig, endDate: e.target.value})} className="w-full px-3 py-3 bg-slate-50 dark:bg-neutral-950 border border-slate-300 dark:border-neutral-700 rounded-xl outline-none focus:ring-2 focus:ring-[#9DCD5A] text-slate-700 dark:text-neutral-200" />
)}{showTrashModal && (

Lixeira de Postagens

{deletedPosts.length > 0 && }
{deletedPosts.length === 0 ? (

A lixeira de postagens está vazia.

) : (
{deletedPosts.map(post => (

{post.title || 'Sem título'}

{formatSafeDate(post.date)} - {POST_PLATFORMS[post.platform]?.label}

))}
)}
)}
); }// --- MODAL DE POSTAGEM --- function PostModal({ post, clients, onClose, onSave, onDelete }) { const [formData, setFormData] = useState({ id: post?.id || `p_${Date.now()}`, title: post?.title || '', description: post?.description || '', date: post?.date || '', time: post?.time || '12:00', platform: post?.platform || 'instagram', status: post?.status || 'rascunho', clientId: post?.clientId || '', isNewPostFlag: post?.isNewPostFlag || false });const canDelete = post?.id && !post?.isNewPostFlag;const handleClose = () => { const plainTitle = String(formData.title || '').replace(/<[^>]+>/g, '').trim(); if (!plainTitle) { if (post?.isNewPostFlag) onClose(); else { alert("O título da postagem não pode ficar vazio."); return; } return; } onSave(formData); };return (
{ if(e.target === e.currentTarget) handleClose(); }}>

{!post?.isNewPostFlag ? 'Editar Postagem' : 'Nova Postagem'}

e.preventDefault()} className="space-y-6">
{Object.values(POST_STATUS).map(s => { const isActive = formData.status === s.id; return ( ); })}
setFormData({...formData, title: e.target.value})} placeholder="Ex: Lançamento de Produto..." autoFocus className="w-full px-4 h-[42px] bg-slate-50 dark:bg-neutral-950 border border-slate-300 dark:border-neutral-700 rounded-xl text-sm text-slate-800 dark:text-neutral-100 outline-none focus:ring-2 focus:ring-[#9DCD5A]" />
setFormData({...formData, date: e.target.value})} className="flex-1 w-full px-3 h-[42px] bg-slate-50 dark:bg-neutral-950 border border-slate-300 dark:border-neutral-700 rounded-xl text-sm text-slate-800 dark:text-neutral-100 outline-none focus:ring-2 focus:ring-[#9DCD5A] cursor-pointer" />
setFormData({...formData, time: e.target.value})} className="flex-1 w-full px-3 h-[42px] bg-slate-50 dark:bg-neutral-950 border border-slate-300 dark:border-neutral-700 rounded-xl text-sm text-slate-800 dark:text-neutral-100 outline-none focus:ring-2 focus:ring-[#9DCD5A] cursor-pointer" />
setFormData({...formData, description: val})} placeholder="Escreva a legenda da postagem..." minHeight="150px" />
{canDelete ? () :
}
); }// --- MODAL DA PAUTA --- function TaskModal({ task, columns, clients, users, currentUser, onClose, onSave, onDelete, showToast, firebaseAuth }) { const [formData, setFormData] = useState({ id: task?.id || `t_${Date.now()}`, title: task?.title || '', description: task?.description || '', columnId: task?.columnId || (columns||[])[0]?.id, assigneeIds: Array.isArray(task?.assigneeIds) ? task.assigneeIds.filter(Boolean) : (task?.assigneeId ? [task.assigneeId] : []), observerIds: Array.isArray(task?.observerIds) ? task.observerIds.filter(Boolean) : [], deadline: task?.deadline || '', deadlineTime: task?.deadlineTime || '', clientId: task?.clientId || '', comments: Array.isArray(task?.comments) ? task.comments.filter(Boolean) : [], subtasks: Array.isArray(task?.subtasks) ? task.subtasks.filter(Boolean) : [], priority: task?.priority || 'media' });const [pendingAttachments, setPendingAttachments] = useState([]); const [isUploading, setIsUploading] = useState(false); const [isCopied, setIsCopied] = useState(false); const [subtaskInput, setSubtaskInput] = useState(''); const [observerSearch, setObserverSearch] = useState(''); const [isObserverDropdownOpen, setIsObserverDropdownOpen] = useState(false); const [assigneeSearch, setAssigneeSearch] = useState(''); const [isAssigneeDropdownOpen, setIsAssigneeDropdownOpen] = useState(false);const commentInputRef = useRef(null); const fileInputRef = useRef(null);const canEdit = true; const canDelete = task?.id && !task?.isNewTaskFlag; // Permite excluir apenas se já existir no BDconst handleAddSubtask = (e) => { if (e.key === 'Enter') { e.preventDefault(); if (subtaskInput.trim() && canEdit) { const updatedData = { ...formData, subtasks: [...formData.subtasks, { id: `st${Date.now()}`, title: subtaskInput, completed: false }] }; setFormData(updatedData); setSubtaskInput(''); onSave(updatedData, true); } } }; const toggleSubtask = (id) => { if (!canEdit) return; const updatedData = { ...formData, subtasks: formData.subtasks.map(st => st.id === id ? { ...st, completed: !st.completed } : st) }; setFormData(updatedData); onSave(updatedData, true); }; const removeSubtask = (id) => { if (!canEdit) return; const updatedData = { ...formData, subtasks: formData.subtasks.filter(st => st.id !== id) }; setFormData(updatedData); onSave(updatedData, true); };const availableObservers = (users||[]).filter(Boolean).filter(u => !formData.observerIds.includes(u?.id) && (u?.name || '').toLowerCase().includes(observerSearch.toLowerCase())); const addObserver = (id) => { if (!canEdit) return; const updatedData = { ...formData, observerIds: [...formData.observerIds, id] }; setFormData(updatedData); setObserverSearch(''); setIsObserverDropdownOpen(false); onSave(updatedData, true); }; const removeObserver = (id) => { if (!canEdit) return; const updatedData = { ...formData, observerIds: formData.observerIds.filter(oId => oId !== id) }; setFormData(updatedData); onSave(updatedData, true); };const availableAssignees = (users||[]).filter(Boolean).filter(u => !formData.assigneeIds.includes(u?.id) && (u?.name || '').toLowerCase().includes(assigneeSearch.toLowerCase())); const addAssignee = (id) => { if (!canEdit) return; const updatedData = { ...formData, assigneeIds: [...formData.assigneeIds, id] }; setFormData(updatedData); setAssigneeSearch(''); setIsAssigneeDropdownOpen(false); onSave(updatedData, true); }; const removeAssignee = (id) => { if (!canEdit) return; const updatedData = { ...formData, assigneeIds: formData.assigneeIds.filter(aId => aId !== id) }; setFormData(updatedData); onSave(updatedData, true); };// GERADOR DE LINK INTELIGENTE const handleCopyLink = () => { const baseUrl = window.location.origin + window.location.pathname; const link = `${baseUrl}?taskId=${task?.id}`; try { navigator.clipboard.writeText(link); } catch(e) { const tempInput = document.createElement('input'); tempInput.value = link; document.body.appendChild(tempInput); tempInput.select(); document.execCommand('copy'); document.body.removeChild(tempInput); } setIsCopied(true); setTimeout(() => setIsCopied(false), 2000); }; const handleFileUpload = async (e) => { const files = Array.from(e.target.files); if (!files.length) return; if (!firebase.apps.length || !firebase.storage) { showToast("Erro: Conecte o Firebase Storage para enviar arquivos.", "error"); return; } setIsUploading(true); const storage = firebase.storage(); const newAttachments = []; for (const file of files) { try { const fileRef = storage.ref().child(`pautas_anexos/${Date.now()}_${file.name}`); const snapshot = await fileRef.put(file); const downloadURL = await snapshot.ref.getDownloadURL(); newAttachments.push({ name: file.name, url: downloadURL, type: file.name.split('.').pop() }); showToast(`Arquivo "${file.name}" anexado com sucesso!`, "success"); } catch (error) { console.error("Erro no upload:", error); showToast(`Erro Firebase: ${error.message}`, "error"); } } setPendingAttachments(prev => [...prev, ...newAttachments]); setIsUploading(false); if (fileInputRef.current) fileInputRef.current.value = ''; }; const handleAddComment = () => { const htmlContent = commentInputRef.current?.innerHTML || ''; if (!htmlContent.trim() && pendingAttachments.length === 0) return; const updatedData = { ...formData, comments: [...formData.comments, { id: `c${Date.now()}`, text: htmlContent, authorId: currentUser?.id, createdAt: new Date().toISOString(), attachments: pendingAttachments, type: 'message' }] }; setFormData(updatedData); setPendingAttachments([]); if (commentInputRef.current) commentInputRef.current.innerHTML = ''; onSave(updatedData, true); };const applyFormat = (command) => { document.execCommand(command, false, null); if (commentInputRef.current) commentInputRef.current.focus(); }; const handleClose = () => { const plainTitle = String(formData.title || '').replace(/<[^>]+>/g, '').trim(); if (!plainTitle) { if (task?.isNewTaskFlag) onClose(); else showToast("O título da pauta não pode ficar vazio.", "error"); return; } onSave(formData, false); // False indicates we are closing the modal };const selectedClient = (clients||[]).find(c => String(c?.id) === String(formData.clientId)); const topColor = selectedClient ? selectedClient.color : '#e5e7eb';return (
{ if(e.target === e.currentTarget) handleClose(); }}>

{!task?.isNewTaskFlag ? 'Detalhes da Pauta' : 'Nova Pauta'}

{!task?.isNewTaskFlag && }
e.preventDefault()} className="space-y-6">
setFormData({...formData, title: val})} placeholder="Ex: Entrevista com candidato..." minHeight="42px" autoFocus={true} />
setFormData({...formData, description: val})} placeholder="Detalhes sobre a tarefa..." minHeight="100px" />
{formData.subtasks.filter(Boolean).length > 0 && (
{formData.subtasks.filter(Boolean).map(st => (
{st.title}
))}
)}
setSubtaskInput(e.target.value)} onKeyDown={handleAddSubtask} placeholder="Escreva e pressione Enter..." className="flex-1 w-full px-4 h-[42px] bg-slate-50 dark:bg-neutral-950 border border-slate-300 dark:border-neutral-700 rounded-xl focus:ring-2 focus:ring-[#9DCD5A] text-sm text-slate-800 dark:text-neutral-100 outline-none transition-colors" />
{ const updatedData = {...formData, deadline: e.target.value}; setFormData(updatedData); onSave(updatedData, true); }} className="flex-1 w-full px-3 h-[42px] bg-slate-50 dark:bg-neutral-950 border border-slate-300 dark:border-neutral-700 rounded-xl text-sm text-slate-800 dark:text-neutral-100 outline-none focus:ring-2 focus:ring-[#9DCD5A] cursor-pointer" onClick={(e) => { try { e.target.showPicker(); } catch (err) {} }} />
{ const updatedData = {...formData, deadlineTime: e.target.value}; setFormData(updatedData); onSave(updatedData, true); }} className="w-28 px-3 h-[42px] bg-slate-50 dark:bg-neutral-950 border border-slate-300 dark:border-neutral-700 rounded-xl text-sm text-slate-800 dark:text-neutral-100 outline-none focus:ring-2 focus:ring-[#9DCD5A] cursor-pointer" onClick={(e) => { try { e.target.showPicker(); } catch (err) {} }} />
{ setAssigneeSearch(e.target.value); setIsAssigneeDropdownOpen(true); }} onFocus={() => setIsAssigneeDropdownOpen(true)} className="flex-1 w-full px-4 h-[42px] bg-slate-50 dark:bg-neutral-950 border border-slate-300 dark:border-neutral-700 rounded-xl focus:ring-2 focus:ring-[#9DCD5A] text-sm text-slate-800 dark:text-neutral-100 outline-none transition-colors" />
{isAssigneeDropdownOpen && assigneeSearch && (
{availableAssignees.length > 0 ? (availableAssignees.map(user => )) : (
Nenhum membro encontrado.
)}
)}
{formData.assigneeIds.filter(Boolean).length > 0 ? (
{formData.assigneeIds.filter(Boolean).map(id => { const user = (users||[]).find(u => String(u?.id) === String(id)); if (!user) return null; return
{(user?.name || '').split(' ')[0]}
; })}
) : (

Sem responsáveis.

)}
{Object.values(PRIORITIES).map(p => { const isActive = formData.priority === p.id; return (); })}
{ setObserverSearch(e.target.value); setIsObserverDropdownOpen(true); }} onFocus={() => setIsObserverDropdownOpen(true)} className="flex-1 w-full px-4 h-[42px] bg-slate-50 dark:bg-neutral-950 border border-slate-300 dark:border-neutral-700 rounded-xl focus:ring-2 focus:ring-[#9DCD5A] text-sm text-slate-800 dark:text-neutral-100 outline-none transition-colors" />
{isObserverDropdownOpen && observerSearch && (
{availableObservers.length > 0 ? (availableObservers.map(user => )) : (
Nenhum membro encontrado.
)}
)}
{formData.observerIds.filter(Boolean).length > 0 ? (
{formData.observerIds.filter(Boolean).map(id => { const user = (users||[]).find(u => String(u?.id) === String(id)); if (!user) return null; return
{(user?.name || '').split(' ')[0]}
; })}
) : (

Sem observadores adicionados.

)}
{canDelete ? () :
}
{/* --- LADO DIREITO DA PAUTA (Atividade) --- */}

Atividade e Comentários

{formData.comments.filter(Boolean).length === 0 ? (

Nenhuma atividade registada.

) : ( formData.comments.filter(Boolean).map(comment => { if (comment.type === 'activity') return (
{comment.text}{formatSafeTime(comment.createdAt)}
); const author = (users||[]).find(u => String(u?.id) === String(comment.authorId)); return (
{author?.name || 'Membro'}
{(Array.isArray(comment.attachments) ? comment.attachments : []).filter(Boolean).length > 0 && (
{(Array.isArray(comment.attachments) ? comment.attachments : []).filter(Boolean).map((file, idx) => ())}
)}
{formatSafeDate(comment.createdAt)} {formatSafeTime(comment.createdAt)}
); }) )}
{pendingAttachments.filter(Boolean).length > 0 && (
{pendingAttachments.filter(Boolean).map((f, i) => {f.name})}
)}
Dica: Escreva @nome para notificar um membro.
); }function ProfileModal({ user, isAdmin, currentMood, onClose, onSave, showToast }) { const [formData, setFormData] = useState({ ...user, mascot: user.mascot || { color: 'yellow', hair: 'none', accessory: 'none' } }); const [activeTab, setActiveTab] = useState('personal'); const [newPassword, setNewPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState('');const updateMascot = (field, value) => setFormData(prev => ({ ...prev, mascot: { ...prev.mascot, [field]: value } }));const handlePasswordChange = async () => { if (newPassword !== confirmPassword) return showToast('As senhas não coincidem.', 'error'); if (newPassword.length < 6) return showToast('A senha deve ter pelo menos 6 caracteres.', 'error'); try { const currentUserAuth = firebase.auth().currentUser; if (currentUserAuth) { await currentUserAuth.updatePassword(newPassword); showToast('Senha alterada com sucesso!', 'success'); setNewPassword(''); setConfirmPassword(''); } else { showToast('Sessão inválida. Faça login novamente.', 'error'); } } catch (error) { if (error.code === 'auth/requires-recent-login') { showToast('Por segurança, precisa fazer logout e entrar novamente antes de mudar a senha.', 'error'); } else { showToast('Erro: ' + error.message, 'error'); } } };return (

Configurações de Perfil

{activeTab === 'personal' && (

Sua Foto de Perfil

setFormData({...formData, name: e.target.value})} className="w-full px-4 py-3 bg-slate-50 dark:bg-neutral-950 border border-slate-300 dark:border-neutral-700 rounded-lg focus:ring-2 focus:ring-[#9DCD5A] text-slate-800 dark:text-neutral-100 outline-none" />
setFormData({...formData, avatarUrl: e.target.value})} placeholder="https://exemplo.com/minha-foto.jpg" className="w-full px-4 py-3 bg-slate-50 dark:bg-neutral-950 border border-slate-300 dark:border-neutral-700 rounded-lg focus:ring-2 focus:ring-[#9DCD5A] text-slate-800 dark:text-neutral-100 outline-none text-sm" />
{isAdmin &&
}
)}{activeTab === 'mascot' && (

Humor: {currentMood === 'happy' ? 'Tudo OK!' : currentMood === 'anxious' ? 'Prazos Apertados!' : 'Atrasado!'}

{['yellow', 'green', 'blue', 'pink', 'purple', 'orange'].map(c =>

{[{ id: 'none', label: 'Nenhum' }, { id: 'short', label: 'Curto' }, { id: 'spiky', label: 'Espetado' }, { id: 'curly', label: 'Enrolado' }, { id: 'long', label: 'Longo' }].map(opt => )}

{[{ id: 'none', label: 'Nenhum' }, { id: 'glasses', label: 'Óculos' }, { id: 'cap', label: 'Boné' }, { id: 'bandana', label: 'Bandana' }].map(acc => )}
)}{activeTab === 'security' && (

Alterar Palavra-passe

Recomendamos alterar a sua senha padrão para uma mais segura.

setNewPassword(e.target.value)} className="w-full px-4 py-3 bg-slate-50 dark:bg-neutral-950 border border-slate-300 dark:border-neutral-700 rounded-lg focus:ring-2 focus:ring-[#9DCD5A] outline-none" placeholder="Mínimo 6 caracteres" />
setConfirmPassword(e.target.value)} className="w-full px-4 py-3 bg-slate-50 dark:bg-neutral-950 border border-slate-300 dark:border-neutral-700 rounded-lg focus:ring-2 focus:ring-[#9DCD5A] outline-none" placeholder="Repita a nova senha" />
)}
{activeTab !== 'security' && ( )}
); }const rootElement = document.getElementById('pauta-manager-root'); if (rootElement) { const root = ReactDOM.createRoot(rootElement); root.render( ); }
Feito com muito 💜 por go7.com.br