// Budgets Module function Budgets() { const [view, setView] = React.useState('list'); const [selected, setSelected] = React.useState(null); const [budgets, setBudgets] = React.useState([]); const [loading, setLoading] = React.useState(true); const [toast, setToast] = React.useState(null); const [filter, setFilter] = React.useState('all'); const [periodType, setPeriodType] = React.useState('todos'); const [periodOffset, setPeriodOffset] = React.useState(0); const [addModal, setAddModal] = React.useState(false); const [editModal, setEditModal] = React.useState(false); const [delConfirm, setDelConfirm] = React.useState(false); const [saving, setSaving] = React.useState(false); const emptyForm = { client_name: '', event_name: '', value: '', status: 'Em elaboração', issued_date: '' }; const [form, setForm] = React.useState(emptyForm); const [editForm, setEditForm] = React.useState(emptyForm); const [items, setItems] = React.useState([{ description: '', qty: 1, unit_price: '', total: 0 }]); const showToast = (msg, type = 'success') => { setToast({ msg, type }); setTimeout(() => setToast(null), 3000); }; const updateItem = (i, field, val) => { setItems(prev => prev.map((item, idx) => { if (idx !== i) return item; const updated = { ...item, [field]: val }; if (field === 'qty' || field === 'unit_price') { const qty = field === 'qty' ? parseFloat(val) || 0 : parseFloat(item.qty) || 0; const unit = field === 'unit_price' ? parseFloat(val) || 0 : parseFloat(item.unit_price) || 0; updated.total = qty * unit; } return updated; })); }; const addItem = () => setItems(prev => [...prev, { description: '', qty: 1, unit_price: '', total: 0 }]); const removeItem = (i) => setItems(prev => prev.filter((_, idx) => idx !== i)); const calcTotal = () => items.reduce((s, it) => s + (it.total || 0), 0); const nextCode = () => { if (budgets.length === 0) return '#001'; const nums = budgets.map(b => parseInt((b.code || '#000').replace('#', '')) || 0); return '#' + String(Math.max(...nums) + 1).padStart(3, '0'); }; const handleCreate = async () => { if (!form.client_name.trim() || !form.event_name.trim()) { showToast('Preencha cliente e evento.', 'danger'); return; } setSaving(true); const code = nextCode(); const today = new Date(); const issued_date = form.issued_date || `${today.getDate().toString().padStart(2,'0')} ${['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'][today.getMonth()]} ${today.getFullYear()}`; const value = parseFloat(form.value) || calcTotal(); const { data: budget, error } = await sb.from('budgets').insert({ code, client_name: form.client_name, event_name: form.event_name, issued_date, value, status: form.status, items_count: items.filter(it => it.description.trim()).length, }).select().single(); if (error) { showToast('Erro ao criar orçamento.', 'danger'); setSaving(false); return; } const validItems = items.filter(it => it.description.trim()); if (validItems.length > 0) { await sb.from('budget_items').insert(validItems.map(it => ({ budget_id: budget.id, description: it.description, qty: parseFloat(it.qty) || 1, unit_price: parseFloat(it.unit_price) || 0, total: it.total || 0, }))); } setBudgets(prev => [budget, ...prev]); setAddModal(false); setForm(emptyForm); setItems([{ description: '', qty: 1, unit_price: '', total: 0 }]); setSaving(false); showToast(`Orçamento ${code} criado com sucesso!`); }; React.useEffect(() => { sb.from('budgets').select('*').order('created_at', { ascending: false }).then(({ data }) => { if (data) setBudgets(data); setLoading(false); }); }, []); const statusVariant = { 'Enviado': 'primary', 'Aprovado': 'success', 'Em elaboração': 'warning', 'Recusado': 'danger' }; // ── Período ────────────────────────────────────────────── const getRange = (type, offset) => { const now = new Date(); if (type === 'todos') return { start: new Date(2000,0,1), end: new Date(2099,11,31), label: 'Todo o período' }; let start, end, label; if (type === 'diario') { const d = new Date(now.getFullYear(), now.getMonth(), now.getDate() + offset); start = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0,0,0); end = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23,59,59); label = d.toLocaleDateString('pt-BR', { weekday:'long', day:'numeric', month:'long', year:'numeric' }); label = label.charAt(0).toUpperCase() + label.slice(1); } else if (type === 'semanal') { const d = new Date(now.getFullYear(), now.getMonth(), now.getDate()); d.setDate(d.getDate() - d.getDay() + offset * 7); start = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0,0,0); end = new Date(d.getFullYear(), d.getMonth(), d.getDate()+6, 23,59,59); const fmt = { day:'numeric', month:'short' }; label = `${start.toLocaleDateString('pt-BR', fmt)} – ${end.toLocaleDateString('pt-BR', { ...fmt, year:'numeric' })}`; } else { const d = new Date(now.getFullYear(), now.getMonth() + offset, 1); start = new Date(d.getFullYear(), d.getMonth(), 1, 0,0,0); end = new Date(d.getFullYear(), d.getMonth()+1, 0, 23,59,59); label = d.toLocaleDateString('pt-BR', { month:'long', year:'numeric' }); label = label.charAt(0).toUpperCase() + label.slice(1); } return { start, end, label }; }; const { start: rangeStart, end: rangeEnd, label: rangeLabel } = getRange(periodType, periodOffset); const inRange = (d) => d && d >= rangeStart && d <= rangeEnd; const byStatus = filter === 'all' ? budgets : budgets.filter(b => b.status === filter); const filtered = byStatus.filter(b => { if (periodType === 'todos') return true; const d = b.created_at ? new Date(b.created_at) : null; return inRange(d); }); const fmtCurrency = v => 'R$ ' + (v || 0).toLocaleString('pt-BR'); const total = filtered.reduce((s, b) => s + (b.value || 0), 0); const aprovados = filtered.filter(b => b.status === 'Aprovado').reduce((s, b) => s + (b.value || 0), 0); const pendentes = filtered.filter(b => b.status !== 'Aprovado' && b.status !== 'Recusado').reduce((s, b) => s + (b.value || 0), 0); const recusados = filtered.filter(b => b.status === 'Recusado').reduce((s, b) => s + (b.value || 0), 0); const [budgetItems, setBudgetItems] = React.useState([]); const [loadingItems, setLoadingItems] = React.useState(false); React.useEffect(() => { if (!selected) return; setLoadingItems(true); setBudgetItems([]); sb.from('budget_items').select('*').eq('budget_id', selected.id).then(({ data }) => { setBudgetItems(data || []); setLoadingItems(false); }); }, [selected?.id]); const openEdit = () => { setEditForm({ client_name: selected.client_name || '', event_name: selected.event_name || '', status: selected.status || 'Em elaboração', issued_date: selected.issued_date || '' }); setEditModal(true); }; const handleEditSave = async () => { if (!editForm.client_name.trim() || !editForm.event_name.trim()) { showToast('Preencha cliente e evento.', 'danger'); return; } setSaving(true); const { data, error } = await sb.from('budgets').update({ client_name: editForm.client_name, event_name: editForm.event_name, status: editForm.status, issued_date: editForm.issued_date, }).eq('id', selected.id).select().single(); setSaving(false); if (error) { showToast('Erro ao salvar.', 'danger'); return; } setBudgets(prev => prev.map(b => b.id === data.id ? data : b)); setSelected(data); setEditModal(false); showToast('Orçamento atualizado!'); }; const handleDelete = async () => { const id = selected.id; const code = selected.code; await sb.from('budget_items').delete().eq('budget_id', id); await sb.from('budgets').delete().eq('id', id); setBudgets(prev => prev.filter(b => b.id !== id)); setDelConfirm(false); setSelected(null); setView('list'); showToast(`Orçamento ${code} excluído.`); }; const exportPDF = () => { const b = selected; const total = budgetItems.reduce((s, i) => s + (parseFloat(i.total) || 0), 0); const fmt = v => 'R$ ' + (parseFloat(v) || 0).toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); const OG = '#C8621A'; // laranja Promoup const itemRows = budgetItems.length === 0 ? `
|
Promoup Marketing E Eventos
PROMOUP MARKETING E EVENTOS LTDA
CNPJ: 61.643.567/0001-70
Rua Antonieta Clotilde, 615 — Jardim Iracema, Fortaleza-CE
CEP 60330-605
|
✉ silvana@promoupmarketing.com.br
☎ +55 (85) 9998-0363
📷 @promouoeventos | 🌐 promoup360.net
${b.issued_date || new Date().toLocaleDateString('pt-BR')}
|
|
Cliente: ${b.client_name}
|
|
Prazo de entrega
Imediato
|
Tipo de evento
${b.event_name}
|
Local do evento
A confirmar
|
Status
${b.status}
|
| Descrição | Unidade | Preço unitário | Qtd. | Preço |
|---|---|---|---|---|
| Total | ${fmt(total)} | |||
|
Meios de pagamento
Boleto, transferência bancária ou pix.
|
PIX
financeiro@promoupmarketing.com.br
|