correção
This commit is contained in:
@@ -29,16 +29,24 @@ export default function Booking() {
|
||||
const [barberId, setBarber] = useState('');
|
||||
const [date, setDate] = useState('');
|
||||
const [slot, setSlot] = useState('');
|
||||
const [step, setStep] = useState(searchParams.get('service') ? 2 : 1);
|
||||
|
||||
// Sincroniza o serviceId se o parâmetro mudar (ex: navegação interna)
|
||||
useEffect(() => {
|
||||
const s = searchParams.get('service');
|
||||
if (s) setService(s);
|
||||
if (s) {
|
||||
setService(s);
|
||||
setStep(2);
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
const selectedService = shop?.services.find((s) => s.id === serviceId);
|
||||
const selectedBarber = shop?.barbers.find((b) => b.id === barberId);
|
||||
|
||||
// Avanço automático por efeito colateral (opcional, mas vamos fazer por clique para ser mais explícito)
|
||||
const nextStep = () => setStep((s) => Math.min(s + 1, 4));
|
||||
const prevStep = () => setStep((s) => Math.max(s - 1, 1));
|
||||
|
||||
/**
|
||||
* Função para gerar horários padrão se não houver horários específicos predefinidos
|
||||
* pelo barbeiro na Base de Dados.
|
||||
@@ -111,173 +119,234 @@ export default function Booking() {
|
||||
};
|
||||
|
||||
const steps = [
|
||||
{ id: 1, label: 'Serviço', icon: Scissors, completed: !!serviceId },
|
||||
{ id: 2, label: 'Barbeiro', icon: User, completed: !!barberId },
|
||||
{ id: 3, label: 'Data', icon: Calendar, completed: !!date },
|
||||
{ id: 4, label: 'Horário', icon: Clock, completed: !!slot },
|
||||
{ id: 1, label: 'Serviço', icon: Scissors, completed: !!serviceId, active: step === 1 },
|
||||
{ id: 2, label: 'Barbeiro', icon: User, completed: !!barberId, active: step === 2 },
|
||||
{ id: 3, label: 'Data', icon: Calendar, completed: !!date, active: step === 3 },
|
||||
{ id: 4, label: 'Horário', icon: Clock, completed: !!slot, active: step === 4 },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900 mb-2">Agendar em {shop.name}</h1>
|
||||
<p className="text-sm text-slate-600">{shop.address}</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900 mb-1">Agendar em {shop.name}</h1>
|
||||
<p className="text-sm text-slate-600">{shop.address}</p>
|
||||
</div>
|
||||
{step > 1 && (
|
||||
<Button variant="outline" size="sm" onClick={prevStep}>
|
||||
Voltar
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Progress Steps */}
|
||||
<div className="flex items-center justify-between max-w-2xl">
|
||||
{steps.map((step, idx) => (
|
||||
<div key={step.id} className="flex items-center flex-1">
|
||||
<div className="flex items-center justify-between max-w-2xl bg-white p-4 rounded-xl shadow-sm border border-slate-100">
|
||||
{steps.map((s, idx) => (
|
||||
<div key={s.id} className="flex items-center flex-1 last:flex-none">
|
||||
<div className="flex flex-col items-center flex-1">
|
||||
<div
|
||||
className={`w-10 h-10 rounded-full flex items-center justify-center border-2 transition-all ${step.completed
|
||||
? 'bg-gradient-to-br from-amber-500 to-amber-600 border-amber-600 text-white shadow-md'
|
||||
: 'bg-white border-slate-300 text-slate-400'
|
||||
<button
|
||||
onClick={() => {
|
||||
if (s.completed || s.id < step) setStep(s.id);
|
||||
}}
|
||||
disabled={!s.completed && s.id > step}
|
||||
className={`w-10 h-10 rounded-full flex items-center justify-center border-2 transition-all ${s.active
|
||||
? 'bg-amber-600 border-amber-600 text-white shadow-lg ring-4 ring-amber-100'
|
||||
: s.completed
|
||||
? 'bg-amber-500 border-amber-500 text-white'
|
||||
: 'bg-white border-slate-200 text-slate-400'
|
||||
}`}
|
||||
>
|
||||
{step.completed ? <CheckCircle2 size={18} /> : <step.icon size={18} />}
|
||||
</div>
|
||||
<span className={`text-xs mt-2 font-medium ${step.completed ? 'text-amber-700' : 'text-slate-500'}`}>
|
||||
{step.label}
|
||||
{s.completed && !s.active ? <CheckCircle2 size={18} /> : <s.icon size={18} />}
|
||||
</button>
|
||||
<span className={`text-[10px] mt-2 font-bold uppercase tracking-wider ${s.active ? 'text-amber-700' : 'text-slate-500'}`}>
|
||||
{s.label}
|
||||
</span>
|
||||
</div>
|
||||
{idx < steps.length - 1 && (
|
||||
<div className={`h-0.5 flex-1 mx-2 ${step.completed ? 'bg-amber-500' : 'bg-slate-200'}`} />
|
||||
<div className={`h-1 flex-1 mx-2 rounded-full ${s.completed ? 'bg-amber-500' : 'bg-slate-100'}`} />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Card className="p-6 space-y-6">
|
||||
{/* Step 1: Service */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Scissors size={18} className="text-amber-600" />
|
||||
<h3 className="text-base font-bold text-slate-900">1. Escolha o serviço</h3>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 gap-3">
|
||||
{shop.services.map((s) => (
|
||||
<button
|
||||
key={s.id}
|
||||
onClick={() => setService(s.id)}
|
||||
className={`p-4 rounded-xl border-2 text-left transition-all ${serviceId === s.id
|
||||
? 'border-amber-500 bg-gradient-to-br from-amber-50 to-amber-100/50 shadow-md scale-[1.02]'
|
||||
: 'border-slate-200 hover:border-amber-300 hover:bg-amber-50/50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="font-bold text-slate-900">{s.name}</div>
|
||||
<div className="text-sm font-bold text-amber-600">{currency(s.price)}</div>
|
||||
</div>
|
||||
<div className="text-xs text-slate-500">Duração: {s.duration} min</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step 2: Barber */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<User size={18} className="text-amber-600" />
|
||||
<h3 className="text-base font-bold text-slate-900">2. Escolha o barbeiro</h3>
|
||||
</div>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{shop.barbers.map((b) => (
|
||||
<button
|
||||
key={b.id}
|
||||
onClick={() => setBarber(b.id)}
|
||||
className={`px-4 py-2.5 rounded-full border-2 text-sm font-medium transition-all ${barberId === b.id
|
||||
? 'border-amber-500 bg-gradient-to-r from-amber-500 to-amber-600 text-white shadow-md'
|
||||
: 'border-slate-200 text-slate-700 hover:border-amber-300 hover:bg-amber-50'
|
||||
}`}
|
||||
>
|
||||
{b.name}
|
||||
{b.specialties.length > 0 && (
|
||||
<span className="ml-2 text-xs opacity-80">· {b.specialties[0]}</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step 3 & 4: Date & Time */}
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar size={18} className="text-amber-600" />
|
||||
<h3 className="text-base font-bold text-slate-900">3. Escolha a data</h3>
|
||||
<Card className="p-6">
|
||||
{/* Step Info Summary (Show what was already selected) */}
|
||||
{step > 1 && selectedService && (
|
||||
<div className="mb-6 p-3 bg-amber-50 border border-amber-100 rounded-lg flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-white rounded-full flex items-center justify-center text-amber-600 shadow-sm">
|
||||
<Scissors size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-amber-600 font-bold uppercase">Serviço Selecionado</p>
|
||||
<p className="text-sm font-bold text-slate-900">{selectedService.name}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Input
|
||||
type="date"
|
||||
value={date}
|
||||
onChange={(e) => setDate(e.target.value)}
|
||||
min={new Date().toISOString().split('T')[0]}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Clock size={18} className="text-amber-600" />
|
||||
<h3 className="text-base font-bold text-slate-900">4. Escolha o horário</h3>
|
||||
</div>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{!barberId || !date ? (
|
||||
<p className="text-sm text-slate-500 py-2">Escolha primeiro o barbeiro e a data.</p>
|
||||
) : availableSlots.length > 0 ? (
|
||||
availableSlots.map((h) => (
|
||||
<button
|
||||
key={h}
|
||||
onClick={() => setSlot(h)}
|
||||
className={`px-4 py-2 rounded-lg border-2 text-sm font-medium transition-all ${slot === h
|
||||
? 'border-amber-500 bg-gradient-to-r from-amber-500 to-amber-600 text-white shadow-md'
|
||||
: 'border-slate-200 text-slate-700 hover:border-amber-300 hover:bg-amber-50'
|
||||
}`}
|
||||
>
|
||||
{h}
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<p className="text-sm text-amber-600 py-2 font-medium">Nenhum horário disponível para esta data.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Summary */}
|
||||
{canSubmit && selectedService && (
|
||||
<div className="pt-4 border-t border-slate-200 space-y-3">
|
||||
<h4 className="text-sm font-bold text-slate-900">Resumo do agendamento</h4>
|
||||
<div className="bg-slate-50 rounded-lg p-4 space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-600">Serviço:</span>
|
||||
<span className="font-semibold text-slate-900">{selectedService.name}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-600">Barbeiro:</span>
|
||||
<span className="font-semibold text-slate-900">{selectedBarber?.name}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-600">Data e hora:</span>
|
||||
<span className="font-semibold text-slate-900">
|
||||
{new Date(date).toLocaleDateString('pt-BR')} às {slot}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between pt-2 border-t border-slate-200">
|
||||
<span className="font-bold text-slate-900">Total:</span>
|
||||
<span className="font-bold text-lg text-amber-600">{currency(selectedService.price)}</span>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-lg font-bold text-amber-600">{currency(selectedService.price)}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button onClick={submit} disabled={!canSubmit} size="lg" className="w-full">
|
||||
{user ? 'Confirmar agendamento' : 'Entrar para agendar'}
|
||||
</Button>
|
||||
{step > 2 && selectedBarber && (
|
||||
<div className="mb-6 p-3 bg-indigo-50 border border-indigo-100 rounded-lg flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-white rounded-full flex items-center justify-center text-indigo-600 shadow-sm">
|
||||
<User size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-indigo-600 font-bold uppercase">Barbeiro</p>
|
||||
<p className="text-sm font-bold text-slate-900">{selectedBarber.name}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Dynamic Step Content */}
|
||||
<div className="space-y-6">
|
||||
{step === 1 && (
|
||||
<div className="space-y-4 animate-in fade-in slide-in-from-bottom-2 duration-300">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="w-8 h-8 bg-amber-600 text-white rounded-lg flex items-center justify-center text-sm font-bold">1</div>
|
||||
<h3 className="text-lg font-bold text-slate-900">Escolha o serviço</h3>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 gap-3">
|
||||
{shop.services.map((s) => (
|
||||
<button
|
||||
key={s.id}
|
||||
onClick={() => {
|
||||
setService(s.id);
|
||||
setStep(2);
|
||||
}}
|
||||
className={`p-4 rounded-xl border-2 text-left transition-all ${serviceId === s.id
|
||||
? 'border-amber-500 bg-amber-50/50 shadow-md ring-2 ring-amber-200'
|
||||
: 'border-slate-100 hover:border-amber-300 hover:bg-amber-50/30'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="font-bold text-slate-900">{s.name}</div>
|
||||
<div className="text-sm font-bold text-amber-600">{currency(s.price)}</div>
|
||||
</div>
|
||||
<div className="text-xs text-slate-500">Duração: {s.duration} min</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 2 && (
|
||||
<div className="space-y-4 animate-in fade-in slide-in-from-bottom-2 duration-300">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="w-8 h-8 bg-amber-600 text-white rounded-lg flex items-center justify-center text-sm font-bold">2</div>
|
||||
<h3 className="text-lg font-bold text-slate-900">Escolha o barbeiro</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||
{shop.barbers.map((b) => (
|
||||
<button
|
||||
key={b.id}
|
||||
onClick={() => {
|
||||
setBarber(b.id);
|
||||
setStep(3);
|
||||
}}
|
||||
className={`p-4 rounded-xl border-2 text-center transition-all flex flex-col items-center gap-3 ${barberId === b.id
|
||||
? 'border-amber-500 bg-amber-50/50 shadow-md ring-2 ring-amber-200'
|
||||
: 'border-slate-100 hover:border-amber-300 hover:bg-amber-50/30'
|
||||
}`}
|
||||
>
|
||||
<div className="w-16 h-16 rounded-full overflow-hidden border-2 border-slate-200 bg-slate-50">
|
||||
{b.imageUrl ? (
|
||||
<img src={b.imageUrl} alt={b.name} className="w-full h-full object-cover" />
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center text-slate-400">
|
||||
<User size={32} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-bold text-slate-900">{b.name}</p>
|
||||
{b.specialties.length > 0 && (
|
||||
<p className="text-xs text-slate-500 mt-1">{b.specialties[0]}</p>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 3 && (
|
||||
<div className="space-y-4 animate-in fade-in slide-in-from-bottom-2 duration-300">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="w-8 h-8 bg-amber-600 text-white rounded-lg flex items-center justify-center text-sm font-bold">3</div>
|
||||
<h3 className="text-lg font-bold text-slate-900">Escolha a data</h3>
|
||||
</div>
|
||||
<div className="max-w-md mx-auto">
|
||||
<Input
|
||||
type="date"
|
||||
value={date}
|
||||
onChange={(e) => {
|
||||
setDate(e.target.value);
|
||||
if (e.target.value) setStep(4);
|
||||
}}
|
||||
min={new Date().toISOString().split('T')[0]}
|
||||
className="text-lg py-6"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 4 && (
|
||||
<div className="space-y-6 animate-in fade-in slide-in-from-bottom-2 duration-300">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="w-8 h-8 bg-amber-600 text-white rounded-lg flex items-center justify-center text-sm font-bold">4</div>
|
||||
<h3 className="text-lg font-bold text-slate-900">Escolha o horário</h3>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-slate-50 rounded-xl mb-4 flex items-center justify-between border border-slate-100">
|
||||
<div className="flex items-center gap-2 text-slate-700">
|
||||
<Calendar size={18} className="text-amber-600" />
|
||||
<span className="font-bold">{new Date(date).toLocaleDateString('pt-PT', { day: 'numeric', month: 'long', year: 'numeric' })}</span>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" onClick={() => setStep(3)}>Alterar</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 md:grid-cols-4 gap-2">
|
||||
{availableSlots.length > 0 ? (
|
||||
availableSlots.map((h) => (
|
||||
<button
|
||||
key={h}
|
||||
onClick={() => setSlot(h)}
|
||||
className={`py-3 rounded-lg border-2 text-sm font-bold transition-all ${slot === h
|
||||
? 'border-amber-600 bg-amber-600 text-white shadow-md'
|
||||
: 'border-slate-200 text-slate-700 hover:border-amber-400 hover:bg-amber-50'
|
||||
}`}
|
||||
>
|
||||
{h}
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<div className="col-span-full py-8 text-center bg-rose-50 rounded-xl border border-rose-100">
|
||||
<p className="text-sm text-rose-600 font-bold">Infelizmente não há horários livres para este dia.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Final Summary & Confirmation */}
|
||||
{canSubmit && (
|
||||
<div className="pt-6 border-t border-slate-200 mt-8 space-y-4">
|
||||
<div className="flex justify-between items-end">
|
||||
<div>
|
||||
<p className="text-xs text-slate-500 font-bold uppercase mb-1">Total a pagar</p>
|
||||
<p className="text-2xl font-black text-slate-900">{currency(selectedService?.price || 0)}</p>
|
||||
</div>
|
||||
<Button onClick={submit} size="lg" className="px-10 bg-indigo-600 hover:bg-indigo-700 shadow-lg shadow-indigo-100">
|
||||
Confirmar Marcação
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user