{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' }}>
)
})}Calendário de Postagens
{monthNames[currentDate.getMonth()]} {currentDate.getFullYear()}
{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" />
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}
{ if(e.target === e.currentTarget) handleClose(); }}>
);
}// --- 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 ({!post?.isNewPostFlag ? 'Editar Postagem' : 'Nova Postagem'}
{canDelete ? () :}
{ if(e.target === e.currentTarget) handleClose(); }}>{/* --- LADO DIREITO DA PAUTA (Atividade) --- */}
{pendingAttachments.filter(Boolean).length > 0 && (
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 ({!task?.isNewTaskFlag ? 'Detalhes da Pauta' : 'Nova Pauta'}
{!task?.isNewTaskFlag && }
{canDelete ? () :}
Atividade e Comentários
{formData.comments.filter(Boolean).length === 0 ? ();
const author = (users||[]).find(u => String(u?.id) === String(comment.authorId));
return ( );
})
)}
Nenhuma atividade registada.
) : ( formData.comments.filter(Boolean).map(comment => { if (comment.type === 'activity') return ({comment.text}{formatSafeTime(comment.createdAt)}
{author?.name || 'Membro'}{(Array.isArray(comment.attachments) ? comment.attachments : []).filter(Boolean).length > 0 && (
{formatSafeDate(comment.createdAt)} {formatSafeTime(comment.createdAt)}{(Array.isArray(comment.attachments) ? comment.attachments : []).filter(Boolean).map((file, idx) => ())}
)}{pendingAttachments.filter(Boolean).map((f, i) => {f.name})}
)}Configurações de Perfil
{activeTab === 'personal' && (
)}{activeTab === 'mascot' && (
)}{activeTab === 'security' && (
)}
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 &&}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 => )}
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' && (
)}