feat: Introduce waitlist functionality and in-app notifications with supporting types and dependencies.
This commit is contained in:
@@ -523,12 +523,13 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
}
|
||||
|
||||
if (status === 'cancelado' && apt) {
|
||||
const waitingUsers = state.waitlists.filter(w => w.barberId === apt.barberId && w.date === apt.date && w.status === 'pending');
|
||||
const waitlistDate = apt.date.split(' ')[0]; // Extract YYYY-MM-DD
|
||||
const waitingUsers = state.waitlists.filter(w => w.barberId === apt.barberId && w.date === waitlistDate && w.status === 'pending');
|
||||
|
||||
if (waitingUsers.length > 0) {
|
||||
const notificationsToInsert = waitingUsers.map(w => ({
|
||||
user_id: w.customerId,
|
||||
message: `Surgiu uma vaga no horário que pretendia a ${w.date}! Corra para fazer a reserva.`
|
||||
message: `Surgiu uma vaga no horário que pretendia a ${waitlistDate} às ${apt.date.split(' ')[1]}! Corra para fazer a reserva.`
|
||||
}));
|
||||
|
||||
await supabase.from('notifications').insert(notificationsToInsert);
|
||||
|
||||
@@ -302,29 +302,10 @@ export default function Booking() {
|
||||
|
||||
{/* Right Side: Slots Grid */}
|
||||
<div className="flex-1 space-y-6">
|
||||
<div className="grid grid-cols-3 sm:grid-cols-4 gap-3">
|
||||
{processedSlots.length > 0 ? (
|
||||
processedSlots.map((s) => (
|
||||
s.isBooked ? (
|
||||
s.waitlistedByMe ? (
|
||||
<div key={s.time} className="h-14 rounded-2xl border-2 border-indigo-200 bg-indigo-50 flex items-center justify-center text-xs font-bold text-indigo-700 opacity-80 cursor-not-allowed">
|
||||
Na Espera ({s.time})
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
key={s.time}
|
||||
onClick={async () => {
|
||||
if (!user) { navigate('/login'); return; }
|
||||
const ok = await joinWaitlist(shop.id, serviceId, barberId, `${date} ${s.time}`);
|
||||
if (ok) alert('Adicionado à lista de espera! Receberá notificação se vagar.');
|
||||
}}
|
||||
className="h-14 rounded-2xl border-2 border-slate-200 bg-slate-100 text-xs font-bold text-slate-600 hover:bg-slate-200 hover:text-slate-800 transition-all flex flex-col items-center justify-center leading-tight shadow-inner"
|
||||
>
|
||||
<span className="text-[9px] uppercase font-black tracking-widest opacity-80">Esgotado</span>
|
||||
<span className="text-[10px] uppercase font-semibold">Lista Espera</span>
|
||||
</button>
|
||||
)
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{processedSlots.some(s => !s.isBooked) ? (
|
||||
<div className="grid grid-cols-3 sm:grid-cols-4 gap-3">
|
||||
{processedSlots.filter(s => !s.isBooked).map((s) => (
|
||||
<button
|
||||
key={s.time}
|
||||
onClick={() => setSlot(s.time)}
|
||||
@@ -336,11 +317,35 @@ export default function Booking() {
|
||||
>
|
||||
{s.time}
|
||||
</button>
|
||||
)
|
||||
))
|
||||
))}
|
||||
</div>
|
||||
) : processedSlots.length > 0 ? (
|
||||
<div className="py-8 px-6 text-center bg-rose-50 rounded-[2rem] border border-rose-100 flex flex-col items-center gap-4">
|
||||
<p className="text-sm text-rose-600 font-black uppercase tracking-widest italic">
|
||||
Todos os horários estão preenchidos para este dia.
|
||||
</p>
|
||||
{user && waitlists.some(w => w.barberId === barberId && w.date === date && w.customerId === user.id && w.status === 'pending') ? (
|
||||
<div className="px-4 py-2 bg-indigo-100 text-indigo-700 rounded-xl text-xs font-bold uppercase tracking-widest">
|
||||
Já estás na lista de espera deste dia!
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
onClick={async () => {
|
||||
if (!user) { navigate('/login'); return; }
|
||||
if (!serviceId) { alert('Selecione primeiro o serviço que pretende.'); return; }
|
||||
const ok = await joinWaitlist(shop.id, serviceId, barberId, date);
|
||||
if (ok) alert('Entraste na lista de espera! Serás notificado se houver desistências.');
|
||||
}}
|
||||
disabled={!serviceId}
|
||||
className={`${serviceId ? 'bg-rose-600 hover:bg-rose-700' : 'bg-slate-300 cursor-not-allowed'} text-white font-black rounded-xl uppercase tracking-widest text-[10px] italic h-10 px-6`}
|
||||
>
|
||||
Entrar na Lista de Espera
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="col-span-full py-12 text-center bg-rose-50 rounded-[2rem] border border-rose-100">
|
||||
<p className="text-sm text-rose-600 font-black uppercase tracking-widest italic">Sem disponibilidade para este dia</p>
|
||||
<div className="col-span-full py-12 text-center bg-indigo-50 rounded-[2rem] border border-indigo-100">
|
||||
<p className="text-sm text-indigo-600 font-black uppercase tracking-widest italic">Selecione primeiro o mestre e a data</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user