// ============================================================ // JuFlow — Serviços + Pacotes // ============================================================ const { useState: useStSV } = React; const USV = window.JFUtils; const SVC_COLORS = ['#FF6B35','#7C3AED','#0891B2','#059669','#D97706','#DB2777','#3B82F6','#14B8A6','#EF4444','#A855F7']; const EMPTY_SVC = { id:null, name:'', category:'', price:100, duration:60, description:'', professionals:[], color:'#FF6B35' }; const Servicos = ({ addToast }) => { const [tab, setTab] = useStSV('servicos'); const [search, setSearch] = useStSV(''); const [catFilter, setCatFilter] = useStSV('all'); const [extraSvcs, setExtraSvcs] = useStSV([]); const [removed, setRemoved] = useStSV([]); const [modal, setModal] = useStSV({ open:false, mode:'create', form: EMPTY_SVC }); const [confirm, setConfirm] = useStSV({ open:false, svc:null }); const allServices = [...window.JF.SERVICES, ...extraSvcs].filter(s => !removed.includes(s.id)); const categories = [...new Set(allServices.map(s => s.category))]; const filtered = allServices.filter(s => { const matchSearch = !search || s.name.toLowerCase().includes(search.toLowerCase()); const matchCat = catFilter === 'all' || s.category === catFilter; return matchSearch && matchCat; }); const openCreate = () => setModal({ open:true, mode:'create', form: { ...EMPTY_SVC, color: SVC_COLORS[Math.floor(Math.random()*SVC_COLORS.length)] } }); const openEdit = (svc) => setModal({ open:true, mode:'edit', form: { ...EMPTY_SVC, ...svc, professionals: [...(svc.professionals || [])] } }); const closeModal = () => setModal(m => ({ ...m, open:false })); const setForm = (k, v) => setModal(m => ({ ...m, form: { ...m.form, [k]: v } })); const toggleProf = (id) => setForm('professionals', modal.form.professionals.includes(id) ? modal.form.professionals.filter(p => p !== id) : [...modal.form.professionals, id]); const saveSvc = () => { const f = modal.form; if (!f.name.trim() || !f.category.trim()) { addToast('Preencha nome e categoria.', 'warning'); return; } if (!f.price || f.price <= 0) { addToast('Informe um valor válido.', 'warning'); return; } if (!f.duration || f.duration <= 0) { addToast('Informe a duração.', 'warning'); return; } if (modal.mode === 'create') { const id = Math.max(0, ...allServices.map(s=>s.id)) + 1; setExtraSvcs(arr => [...arr, { ...f, id }]); addToast(`Serviço "${f.name}" criado!`, 'success'); } else { const isFromBase = window.JF.SERVICES.some(s => s.id === f.id); if (isFromBase) { Object.assign(window.JF.SERVICES.find(s=>s.id===f.id), f); setExtraSvcs(arr => [...arr]); } else { setExtraSvcs(arr => arr.map(s => s.id === f.id ? { ...s, ...f } : s)); } addToast('Serviço atualizado!', 'success'); } closeModal(); }; const duplicateSvc = (svc) => { const id = Math.max(0, ...allServices.map(s=>s.id)) + 1; setExtraSvcs(arr => [...arr, { ...svc, id, name: svc.name + ' (cópia)' }]); addToast(`Cópia de "${svc.name}" criada.`, 'success'); }; const askDelete = (svc) => setConfirm({ open:true, svc }); const confirmDelete = () => { setRemoved(arr => [...arr, confirm.svc.id]); addToast(`Serviço "${confirm.svc.name}" excluído.`, 'success'); }; return (
s+sv.price,0)/allServices.length : 0)} icon="DollarSign" color="#059669" subtitle="por serviço" />
{tab === 'servicos' && ( <>
{categories.map(c => ( ))}
{filtered.map(svc => { const profs = (svc.professionals || []).map(id => USV.getProfessional(id)).filter(Boolean); return (
{ e.currentTarget.style.boxShadow='0 8px 24px rgba(10,31,68,.12)'; e.currentTarget.style.transform='translateY(-2px)'; }} onMouseLeave={e=>{ e.currentTarget.style.boxShadow='0 2px 8px rgba(10,31,68,.06)'; e.currentTarget.style.transform='none'; }} >
{svc.category}

{svc.name}

} items={[ {label:'Editar',icon:'Edit2',onClick:()=>openEdit(svc)}, {label:'Duplicar',icon:'Copy',onClick:()=>duplicateSvc(svc)}, 'divider', {label:'Excluir',icon:'Trash2',onClick:()=>askDelete(svc),danger:true}, ]} />

{svc.description}

{USV.formatCurrency(svc.price)}
valor
{svc.duration}min
duração
Profissionais
{profs.length === 0 ? ( Nenhum profissional associado ) : profs.map(p => (
{p.name.split(' ')[0]}
))}
); })}
)} {tab === 'pacotes' && (
{window.JF.PACKAGES.map(pkg => { const svc = USV.getService(pkg.serviceId); const discount = Math.round((1 - pkg.price/pkg.originalPrice)*100); return (
Pacote

{pkg.name}

-{discount}%

{pkg.description}

{pkg.totalSessions}
sessões
{USV.formatCurrency(pkg.price)}
{USV.formatCurrency(pkg.originalPrice)}
{svc &&
Serviço: {svc.name}
}
); })}
{ e.currentTarget.style.borderColor='#FF6B35'; e.currentTarget.style.background='#FFF0EA'; }} onMouseLeave={e=>{ e.currentTarget.style.borderColor='#D1D5DB'; e.currentTarget.style.background='transparent'; }} onClick={()=>addToast('Modal de novo pacote em breve!','info')} > Novo Pacote
)} {/* Service form modal */} } >
{/* Color + Name + Category */}
{SVC_COLORS.slice(0,5).map(c => (
{SVC_COLORS.slice(5).map(c => (
setForm('name',v)} icon="Scissors" required autoFocus style={{ flex:1 }} />
{/* Category (with suggestions) */}
setForm('category',v)} icon="Tag" required placeholder="Ex: Massagem, Estética..." /> {categories.length > 0 && (
Sugestões: {categories.map(c => ( ))}
)}
{/* Price + Duration */}
setForm('price', Number(e.target.value))} style={{ padding:'9px 12px', borderRadius:8, fontSize:13, border:'1.5px solid #E2E8F0', outline:'none', color:'#0A1F44', background:'#fff' }} />
setForm('duration', Number(e.target.value))} style={{ padding:'9px 12px', borderRadius:8, fontSize:13, border:'1.5px solid #E2E8F0', outline:'none', color:'#0A1F44', background:'#fff' }} />
{/* Description */}