agendapro/src/components/Services.tsx

190 lines
9.4 KiB
TypeScript

import { useState, useEffect } from 'react';
import { Plus, X, Clock, DollarSign, Edit, Trash2, AlertTriangle } from 'lucide-react';
const categories = [
{ id: '1', name: 'Cabelo', icon: '✂️' },
{ id: '2', name: 'Barba', icon: '🧔' },
{ id: '3', name: 'Combos', icon: '⭐' },
{ id: '4', name: 'Manicure', icon: '💅' },
{ id: '5', name: 'Massagem', icon: '💆' },
];
interface Service {
id: string; catId: string; name: string; desc: string; duration: number; price: number; active: boolean;
}
const initialServices: Service[] = [
{ id: '1', catId: '1', name: 'Corte Masculino', desc: 'Corte moderno com lavagem e finalização', duration: 40, price: 55, active: true },
{ id: '2', catId: '1', name: 'Corte Infantil', desc: 'Corte para crianças até 12 anos', duration: 30, price: 40, active: true },
{ id: '3', catId: '2', name: 'Barba Completa', desc: 'Aparar, modelar e hidratação', duration: 30, price: 40, active: true },
{ id: '4', catId: '2', name: 'Design de Sobrancelha', desc: 'Alinhamento com navalha', duration: 15, price: 25, active: true },
{ id: '5', catId: '3', name: 'Combo Corte + Barba', desc: 'Corte masculino + barba completa', duration: 60, price: 85, active: true },
{ id: '6', catId: '4', name: 'Manicure Completa', desc: 'Lixar, cuticular e esmaltação', duration: 45, price: 50, active: true },
{ id: '7', catId: '4', name: 'Pedicure', desc: 'Tratamento completo para os pés', duration: 50, price: 55, active: true },
{ id: '8', catId: '5', name: 'Massagem Relaxante', desc: 'Massagem corpo inteiro 60min', duration: 60, price: 120, active: true },
{ id: '9', catId: '5', name: 'Quick Massage', desc: 'Massagem expressa 20min', duration: 20, price: 45, active: true },
];
const emptyService: Service = { id: '', catId: '1', name: '', desc: '', duration: 30, price: 50, active: true };
export default function Services() {
const [services, setServices] = useState<Service[]>(initialServices);
const [showModal, setShowModal] = useState(false);
const [selectedCat, setSelectedCat] = useState<string | null>(null);
const [editingService, setEditingService] = useState<Service | null>(null);
const [formData, setFormData] = useState<Service>(emptyService);
const [deleteConfirm, setDeleteConfirm] = useState<string | null>(null);
useEffect(() => {
const savedName = localStorage.getItem('agendapro_tenant_name');
if (savedName) {
setServices([
{ id: '1', catId: '1', name: 'Corte de Cabelo (Exemplo)', desc: 'Corte padrão do estabelecimento', duration: 30, price: 40, active: true }
]);
}
}, []);
const filtered = selectedCat ? services.filter(s => s.catId === selectedCat) : services;
const openAdd = () => {
setEditingService(null);
setFormData({ ...emptyService, id: Date.now().toString() });
setShowModal(true);
};
const openEdit = (svc: Service) => {
setEditingService(svc);
setFormData({ ...svc });
setShowModal(true);
};
const handleSave = () => {
if (!formData.name.trim()) return;
if (editingService) {
setServices(prev => prev.map(s => s.id === editingService.id ? { ...formData } : s));
} else {
setServices(prev => [...prev, { ...formData }]);
}
setShowModal(false);
setEditingService(null);
};
const handleDelete = (id: string) => {
setServices(prev => prev.filter(s => s.id !== id));
setDeleteConfirm(null);
};
return (
<div className="slide-up">
<div className="flex-between mb-6">
<div className="flex-gap" style={{ gap: '8px', flexWrap: 'wrap' }}>
<button className={`btn btn-sm ${!selectedCat ? 'btn-primary' : 'btn-secondary'}`} onClick={() => setSelectedCat(null)}>
Todos
</button>
{categories.filter(c => services.some(s => s.catId === c.id)).map(c => (
<button key={c.id} className={`btn btn-sm ${selectedCat === c.id ? 'btn-primary' : 'btn-secondary'}`}
onClick={() => setSelectedCat(c.id)}>
{c.icon} {c.name}
</button>
))}
</div>
<button className="btn btn-primary" onClick={openAdd}>
<Plus size={16} /> Novo Serviço
</button>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: '16px' }}>
{filtered.map(svc => (
<div key={svc.id} className="card" style={{ padding: '20px', position: 'relative' }}>
{deleteConfirm === svc.id && (
<div style={{
position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.85)', borderRadius: 'var(--radius-lg)',
display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '16px', zIndex: 10
}}>
<AlertTriangle size={32} color="var(--warning)" />
<p style={{ fontSize: '14px', fontWeight: 600, textAlign: 'center' }}>Excluir &quot;{svc.name}&quot;?</p>
<div style={{ display: 'flex', gap: '8px' }}>
<button className="btn btn-sm btn-danger" onClick={() => handleDelete(svc.id)}>Sim, excluir</button>
<button className="btn btn-sm btn-secondary" onClick={() => setDeleteConfirm(null)}>Cancelar</button>
</div>
</div>
)}
<div className="flex-between" style={{ marginBottom: '12px' }}>
<span style={{ fontSize: '24px' }}>{categories.find(c => c.id === svc.catId)?.icon}</span>
<div style={{ display: 'flex', gap: '4px' }}>
<button className="btn-ghost btn-icon" title="Editar" onClick={() => openEdit(svc)}><Edit size={14} /></button>
<button className="btn-ghost btn-icon" title="Excluir" style={{ color: 'var(--danger)' }}
onClick={() => setDeleteConfirm(svc.id)}><Trash2 size={14} /></button>
</div>
</div>
<h3 style={{ fontSize: '16px', fontWeight: 700, marginBottom: '6px' }}>{svc.name}</h3>
<p style={{ fontSize: '13px', color: 'var(--text-secondary)', marginBottom: '16px' }}>{svc.desc}</p>
<div className="flex-between">
<div className="flex-gap gap-sm">
<span className="badge badge-purple"><Clock size={12} /> {svc.duration}min</span>
</div>
<span style={{ fontSize: '20px', fontWeight: 800, color: 'var(--accent)' }}>
R$ {svc.price.toFixed(2)}
</span>
</div>
</div>
))}
</div>
{filtered.length === 0 && (
<div className="empty-state">
<h3>Nenhum serviço encontrado</h3>
<p>Adicione um serviço ou selecione outra categoria.</p>
</div>
)}
{showModal && (
<div className="modal-overlay" onClick={() => setShowModal(false)}>
<div className="modal" onClick={e => e.stopPropagation()}>
<div className="modal-header">
<h2 className="modal-title">{editingService ? 'Editar Serviço' : 'Novo Serviço'}</h2>
<button className="btn-ghost btn-icon" onClick={() => setShowModal(false)}><X size={20} /></button>
</div>
<div className="modal-body">
<div className="form-group">
<label className="form-label">Categoria</label>
<select className="form-select" value={formData.catId} onChange={e => setFormData(p => ({ ...p, catId: e.target.value }))}>
{categories.map(c => <option key={c.id} value={c.id}>{c.icon} {c.name}</option>)}
</select>
</div>
<div className="form-group">
<label className="form-label">Nome do serviço *</label>
<input className="form-input" placeholder="Ex: Corte Masculino" value={formData.name}
onChange={e => setFormData(p => ({ ...p, name: e.target.value }))} />
</div>
<div className="form-group">
<label className="form-label">Descrição</label>
<textarea className="form-textarea" placeholder="Descreva o serviço..." value={formData.desc}
onChange={e => setFormData(p => ({ ...p, desc: e.target.value }))} />
</div>
<div className="grid-2">
<div className="form-group">
<label className="form-label"><Clock size={14} style={{ display: 'inline', marginRight: 4 }} />Duração (min)</label>
<input className="form-input" type="number" value={formData.duration}
onChange={e => setFormData(p => ({ ...p, duration: Number(e.target.value) }))} />
</div>
<div className="form-group">
<label className="form-label"><DollarSign size={14} style={{ display: 'inline', marginRight: 4 }} />Preço (R$)</label>
<input className="form-input" type="number" step="0.01" value={formData.price}
onChange={e => setFormData(p => ({ ...p, price: Number(e.target.value) }))} />
</div>
</div>
</div>
<div className="modal-footer">
<button className="btn btn-secondary" onClick={() => setShowModal(false)}>Cancelar</button>
<button className="btn btn-primary" onClick={handleSave}>
{editingService ? 'Salvar Alterações' : 'Salvar Serviço'}
</button>
</div>
</div>
</div>
)}
</div>
);
}