// Freelancers Module function Freelancers() { const [freelancers, setFreelancers] = React.useState([]); const [loading, setLoading] = React.useState(true); const [selected, setSelected] = React.useState(null); const [addModal, setAddModal] = React.useState(false); const [editModal, setEditModal] = React.useState(false); const [delConfirm, setDelConfirm] = React.useState(false); const [payModal, setPayModal] = React.useState(false); const [saving, setSaving] = React.useState(false); const [savingPay, setSavingPay] = React.useState(false); const emptyForm = { name: '', role: '', city: '', phone: '', email: '', rate: '', skills: '', status: 'Disponível' }; const [form, setForm] = React.useState(emptyForm); const [editForm, setEditForm] = React.useState(emptyForm); const emptyPay = { tipo: 'total', valor: '', metodo: 'PIX', descricao: '' }; const [payForm, setPayForm] = React.useState(emptyPay); const [payments, setPayments] = React.useState([]); const [toast, setToast] = React.useState(null); const showToast = (msg, type = 'success') => { setToast({ msg, type }); setTimeout(() => setToast(null), 3000); }; const handleCreate = async () => { if (!form.name.trim() || !form.role.trim()) { showToast('Preencha nome e especialidade.', 'danger'); return; } setSaving(true); const skills = form.skills.split(',').map(s => s.trim()).filter(Boolean); const { data, error } = await sb.from('freelancers').insert({ name: form.name, role: form.role, city: form.city, phone: form.phone, email: form.email, rate: parseFloat(form.rate) || 0, skills, status: form.status, rating: 5.0, events_count: 0, }).select().single(); setSaving(false); if (error) { showToast('Erro ao adicionar freelancer.', 'danger'); return; } setFreelancers(prev => [...prev, data].sort((a, b) => a.name.localeCompare(b.name))); setAddModal(false); setForm(emptyForm); showToast(`${data.name} adicionado com sucesso!`); }; const openEdit = (f) => { setEditForm({ name: f.name, role: f.role || '', city: f.city || '', phone: f.phone || '', email: f.email || '', rate: String(f.rate || ''), skills: (f.skills || []).join(', '), status: f.status || 'Disponível' }); setEditModal(true); }; const handleEditSave = async () => { if (!editForm.name.trim()) { showToast('Preencha o nome.', 'danger'); return; } setSaving(true); const skills = editForm.skills.split(',').map(s => s.trim()).filter(Boolean); const { data, error } = await sb.from('freelancers').update({ name: editForm.name, role: editForm.role, city: editForm.city, phone: editForm.phone, email: editForm.email, rate: parseFloat(editForm.rate) || 0, skills, status: editForm.status, }).eq('id', selected.id).select().single(); setSaving(false); if (error) { showToast('Erro ao salvar.', 'danger'); return; } setFreelancers(prev => prev.map(f => f.id === data.id ? data : f).sort((a, b) => a.name.localeCompare(b.name))); setSelected(data); setEditModal(false); showToast('Freelancer atualizado!'); }; const handleDelete = async () => { const id = selected.id; const nome = selected.name; await sb.from('freelancers').delete().eq('id', id); setFreelancers(prev => prev.filter(f => f.id !== id)); setDelConfirm(false); setSelected(null); showToast(`${nome} excluído.`); }; React.useEffect(() => { sb.from('freelancers').select('*').order('name').then(({ data }) => { if (data) setFreelancers(data); setLoading(false); }); }, []); React.useEffect(() => { setPayments(selected?.payments || []); }, [selected?.id]); React.useEffect(() => { if (payModal && payForm.tipo === 'total' && selected) { setPayForm(f => ({ ...f, valor: String(selected.rate || '') })); } if (payModal && payForm.tipo === 'parcial') { setPayForm(f => ({ ...f, valor: '' })); } }, [payForm.tipo, payModal]); const openPayModal = () => { setPayForm({ tipo: 'total', valor: String(selected?.rate || ''), metodo: 'PIX', descricao: '' }); setPayModal(true); }; const handlePayment = async () => { const valor = parseFloat(String(payForm.valor).replace(',', '.')) || 0; if (valor <= 0) { showToast('Informe um valor válido.', 'danger'); return; } setSavingPay(true); const novoPag = { id: Date.now(), data: new Date().toLocaleDateString('pt-BR'), valor, metodo: payForm.metodo, tipo: payForm.tipo, descricao: payForm.descricao, }; const updated = [...payments, novoPag]; const { error } = await sb.from('freelancers').update({ payments: updated }).eq('id', selected.id); setSavingPay(false); if (!error) { setPayments(updated); setFreelancers(prev => prev.map(f => f.id === selected.id ? { ...f, payments: updated } : f)); setSelected(prev => ({ ...prev, payments: updated })); } setPayModal(false); setPayForm(emptyPay); showToast(`R$ ${valor.toLocaleString('pt-BR', { minimumFractionDigits: 2 })} registrado via ${novoPag.metodo}!`); }; const statusColor = { 'Disponível': 'success', 'Escalado': 'primary', 'Indisponível': 'danger' }; const StarRating = ({ rating }) => (
{rating}
); if (selected) { const f = selected; return (
setSelected(null)} style={{ transform: 'rotate(180deg)' }}>

Perfil do Freelancer

{f.name}
{f.role} · {f.city}
{f.events_count} eventos {f.status}
{(f.skills || []).map(s => {s})}
Mensagem showToast('Freelancer convidado!')}>Convidar openEdit(f)}>Editar setDelConfirm(true)}>Excluir
{[ { label: 'Cachê/diária', value: `R$ ${(f.rate || 0).toLocaleString('pt-BR')}` }, { label: 'E-mail', value: f.email }, { label: 'Telefone', value: f.phone }, ].map(i => (
{i.label}
{i.value}
))}
Histórico de eventos
Próximos eventos
Nenhum evento agendado
Pagamentos
{payments.length > 0 && (
R$ {payments.reduce((s, p) => s + p.valor, 0).toLocaleString('pt-BR', { minimumFractionDigits: 2 })}
)}
{/* Histórico */} {payments.length === 0 ? (
Nenhum pagamento registrado.
) : (
{[...payments].reverse().map(p => { const metodoColor = { PIX: '#10B981', Cartão: '#3B82F6', Dinheiro: '#F59E0B' }; return (
R$ {p.valor.toLocaleString('pt-BR', { minimumFractionDigits: 2 })} {p.metodo} {p.tipo === 'parcial' && Parcial}
{p.data}
{p.descricao &&
{p.descricao}
}
); })}
)} Registrar pagamento
{/* Modal registrar pagamento */} { setPayModal(false); setPayForm(emptyPay); }} title="Registrar Pagamento" width={460}>
{/* Info do freelancer */}
{selected?.name}
Cachê: R$ {(selected?.rate || 0).toLocaleString('pt-BR')}
{/* Tipo: Total / Parcial */}
Tipo de pagamento
{['total', 'parcial'].map(t => ( ))}
{payForm.tipo === 'parcial' && (
Informe o valor que será pago agora.
)}
{/* Valor */} setPayForm(f => ({ ...f, valor: v }))} type="number" placeholder="Ex: 1800" /> {/* Forma de pagamento */}
Forma de pagamento
{[ { id: 'PIX', emoji: '⚡', color: '#10B981' }, { id: 'Cartão', emoji: '💳', color: '#3B82F6' }, { id: 'Dinheiro', emoji: '💵', color: '#F59E0B' }, ].map(m => ( ))}
{/* Descrição */} setPayForm(f => ({ ...f, descricao: v }))} placeholder="Ex: Evento Congresso Tech — 28/04" />
{ setPayModal(false); setPayForm(emptyPay); }}>Cancelar {savingPay ? 'Salvando…' : 'Confirmar pagamento'}
{/* Modal editar */} setEditModal(false)} title="Editar Freelancer" width={560}>
setEditForm(f => ({ ...f, name: v }))} /> setEditForm(f => ({ ...f, role: v }))} />
setEditForm(f => ({ ...f, email: v }))} /> setEditForm(f => ({ ...f, phone: v }))} />
setEditForm(f => ({ ...f, city: v }))} /> setEditForm(f => ({ ...f, rate: v }))} type="number" />
setEditForm(f => ({ ...f, skills: v }))} /> setForm(f => ({ ...f, name: v }))} placeholder="Ex: Carlos Melo" /> setForm(f => ({ ...f, role: v }))} placeholder="Ex: DJ / Sonorização" />
setForm(f => ({ ...f, email: v }))} placeholder="Ex: carlos@email.com" /> setForm(f => ({ ...f, phone: v }))} placeholder="Ex: (11) 99999-0000" />
setForm(f => ({ ...f, city: v }))} placeholder="Ex: São Paulo, SP" /> setForm(f => ({ ...f, rate: v }))} placeholder="Ex: 1800" type="number" />
setForm(f => ({ ...f, skills: v }))} placeholder="Ex: DJ, Sonorização, Iluminação" />