diff --git a/web/src/pages/Booking.tsx b/web/src/pages/Booking.tsx index 4859c72..89e755b 100644 --- a/web/src/pages/Booking.tsx +++ b/web/src/pages/Booking.tsx @@ -13,11 +13,37 @@ export default function Booking() { const navigate = useNavigate(); // Extração das ferramentas vitais do Context global da aplicação - const { shops, createAppointment, user, appointments, waitlists, joinWaitlist } = useApp(); + const { shops, shopsReady, createAppointment, user, appointments, waitlists, joinWaitlist } = useApp(); // Procura a barbearia acedida (com base no URL parameter ':id') const shop = useMemo(() => shops.find((s) => s.id === id), [shops, id]); + + if (!shopsReady || (shops.length === 0 && !shop)) { + return ( +
+
+

A preparar reserva...

+
+ ); + } + + if (!shop) { + return ( +
+
+ +
+
+

Espaço não encontrado

+

Não foi possível iniciar o agendamento para este estabelecimento.

+
+ +
+ ); + } // Estados para as escolhas parciais do utilizador const [serviceId, setService] = useState(searchParams.get('service') || ''); const [barberId, setBarber] = useState(''); @@ -75,7 +101,6 @@ export default function Booking() { }); }, [selectedBarber, date, barberId, appointments, user, waitlists]); - if (!shop) return
Barbearia não encontrada
; const canSubmit = serviceId && barberId && date && slot; diff --git a/web/src/pages/Profile.tsx b/web/src/pages/Profile.tsx index b1c6d93..37eaa20 100644 --- a/web/src/pages/Profile.tsx +++ b/web/src/pages/Profile.tsx @@ -91,6 +91,8 @@ export default function Profile() { setReviewTarget(null) } + const [activeTab, setActiveTab] = useState<'favoritos' | 'agenda' | 'pedidos'>('favoritos') + if (loadingAuth) { return (
@@ -135,27 +137,27 @@ export default function Profile() { /> )} -
+
{/* Cabeçalho do Perfil */}
-
+
-
- +
+
-
-
- +
+
+ Utilizador Registado
-

+

{displayName}

-

{authEmail}

+

{authEmail}

@@ -186,167 +188,191 @@ export default function Profile() { )} - {/* ❤️ Barbearias Favoritas - Horizontal Scroll or Grid */} - {favoriteShops.length > 0 && ( -
-
-
- -

Cofre de Favoritos

-
- {favoriteShops.length} Espaços -
-
- {favoriteShops.map((shop) => ( - - -
-
- {shop.imageUrl ? ( - {shop.name} - ) : ( -
- + {/* Navegação por Tabs no Perfil */} +
+
+ {[ + { id: 'favoritos', label: 'Favoritos', icon: Heart, count: favoriteShops.length }, + { id: 'agenda', label: 'Minha Agenda', icon: Calendar, count: myAppointments.length }, + { id: 'pedidos', label: 'Pedidos', icon: ShoppingBag, count: myOrders.length }, + ].map((t) => ( + + ))} +
+
+ +
+ {activeTab === 'favoritos' && ( +
+ {!favoriteShops.length ? ( + + +

Sem Favoritos

+

Ainda não escolheste os teus espaços preferidos.

+ +
+ ) : ( +
+ {favoriteShops.map((shop) => ( + + +
+
+ {shop.imageUrl ? ( + {shop.name} + ) : ( +
+ +
+ )}
- )} -
-
-

{shop.name}

-
-
- - {shop.rating.toFixed(1)} -
-
- - {shop.address.split(',')[0]} -
-
-
-
- - - ))} -
-
- )} - -
- {/* Main Column: Appointments */} -
-
-
- -

Minha Agenda

-
-
- - {!myAppointments.length ? ( - - -

Sem Reservas

-

Sua jornada de estilo ainda não começou.

- -
- ) : ( -
- {myAppointments.map((a) => { - const shop = shops.find((s) => s.id === a.shopId) - const service = shop?.services.find((s) => s.id === a.serviceId) - const canReview = a.status === 'concluido' && !reviewedAppointments.has(a.id) - - return ( - -
-
-
-
-

{shop?.name}

-
- {statusLabel[a.status]} -
+
+

{shop.name}

+
+
+ + {shop.rating.toFixed(1)} +
+
+ + {shop.address.split(',')[0]}
-

{a.date}

-
- {currency(a.total)} -
-
- -
- {service && ( -
- - {service.name} · {service.duration} MIN -
- )} - - {canReview && ( - - )} - - {a.status === 'concluido' && reviewedAppointments.has(a.id) && ( -
- - Avaliado -
- )} -
-
- - ) - })} -
- )} -
- - {/* Side Column: Orders */} -
-
-
- -

Pedidos

-
-
- - {!myOrders.length ? ( - -

Sem encomendas efetuadas.

-
- ) : ( -
- {myOrders.map((o) => { - const shop = shops.find((s) => s.id === o.shopId) - return ( - -
-
-

{shop?.name}

-
{currency(o.total)}
+
-
-
- {o.items.length} {o.items.length === 1 ? 'Item' : 'Itens'} + + + ))} +
+ )} +
+ )} + + {activeTab === 'agenda' && ( +
+ {!myAppointments.length ? ( + + +

Sem Reservas

+

Sua jornada de estilo ainda não começou.

+ +
+ ) : ( +
+ {myAppointments.map((a) => { + const shop = shops.find((s) => s.id === a.shopId) + const service = shop?.services.find((s) => s.id === a.serviceId) + const canReview = a.status === 'concluido' && !reviewedAppointments.has(a.id) + + return ( + +
+
+
+
+

{shop?.name}

+
+ {statusLabel[a.status]} +
+
+

{a.date}

+
+
+ {currency(a.total)} +
-
- {statusLabel[o.status]} + +
+ {service && ( +
+ + {service.name} · {service.duration} MIN +
+ )} + + {canReview && ( + + )} + + {a.status === 'concluido' && reviewedAppointments.has(a.id) && ( +
+ + Avaliado +
+ )}
-
-
- ) - })} -
- )} -
+ + ) + })} +
+ )} + + )} + + {activeTab === 'pedidos' && ( +
+ {!myOrders.length ? ( + + +

Sem encomendas efetuadas.

+
+ ) : ( +
+ {myOrders.map((o) => { + const shop = shops.find((s) => s.id === o.shopId) + return ( + +
+
+

{shop?.name}

+
{currency(o.total)}
+
+

{new Date(o.createdAt).toLocaleString('pt-PT')}

+
+
+ {o.items.length} {o.items.length === 1 ? 'Item' : 'Itens'} +
+
+ {statusLabel[o.status]} +
+
+
+
+ ) + })} +
+ )} +
+ )}
diff --git a/web/src/pages/ShopDetails.tsx b/web/src/pages/ShopDetails.tsx index f00a5bf..a648c14 100644 --- a/web/src/pages/ShopDetails.tsx +++ b/web/src/pages/ShopDetails.tsx @@ -23,16 +23,31 @@ export default function ShopDetails() { const [tab, setTab] = useState<'servicos' | 'produtos' | 'barbeiros' | 'detalhes'>('servicos'); const [imageOpen, setImageOpen] = useState(false); - if (!shopsReady) { + if (!shopsReady || (shops.length === 0 && !shop)) { return ( -
-
-

A carregar detalhes...

+
+
+

A carregar detalhes...

); } - if (!shop) return
Barbearia não encontrada.
; + if (!shop) { + return ( +
+
+ +
+
+

Barbearia não encontrada

+

O espaço que procura pode ter sido removido ou o link está incorreto.

+
+ +
+ ); + } const mapUrl = `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent( `${shop.name} ${shop.address}` )}`;