190 lines
9.4 KiB
TypeScript
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 "{svc.name}"?</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>
|
|
);
|
|
}
|