diff --git a/src/pages/Profile.tsx b/src/pages/Profile.tsx index f3f9f8d..10c2a0d 100644 --- a/src/pages/Profile.tsx +++ b/src/pages/Profile.tsx @@ -10,6 +10,7 @@ import { currency } from '../lib/format'; import { RootStackParamList } from '../navigation/types'; import { supabase } from '../lib/supabase'; import { colors, radius, shadows } from '../lib/theme'; +import { Ionicons } from '@expo/vector-icons'; const statusColor: Record = { pendente: colors.primary, @@ -25,8 +26,12 @@ const statusLabel: Record = { cancelado: 'Cancelado', }; -type ClientTab = 'favoritos' | 'agenda' | 'pedidos'; -type BarberTab = 'reviews' | 'agenda_pessoal' | 'estatisticas'; +const statusIconName: Record = { + pendente: 'hourglass-outline', + confirmado: 'checkmark-circle-outline', + concluido: 'checkbox-outline', + cancelado: 'close-circle-outline', +}; export default function Profile() { const navigation = useNavigation>(); @@ -115,22 +120,29 @@ export default function Profile() { }; return ( - - - {/* Header Perfil */} + + + + {/* Profile Card Header - Premium Styling */} - + {user.name.charAt(0).toUpperCase()} - {user.name} - {user.email} + {user.name} + {user.email} + {isBarber ? 'Gestor de Espaço' : 'Cliente'} + {/* Stats Strip */} {activeAppointmentsCount} @@ -141,20 +153,30 @@ export default function Profile() { {isBarber ? 'Reviews' : 'Favoritos'} - {isBarber ? myShop?.rating.toFixed(1) || '0.0' : currency(totalOrdersValue)} - {isBarber ? 'Rating' : 'Compras'} + + {isBarber ? (myShop?.rating || 0).toFixed(1) : currency(totalOrdersValue)} + + {isBarber ? 'Rating ★' : 'Compras'} + {/* Notifications Section */} {myNotifications.length > 0 && ( - Notificações - + + + Notificações Recentes + + {myNotifications.map((n) => ( + + + {!n.read && } + {n.message} {!n.read && ( - markNotificationRead(n.id)}> + markNotificationRead(n.id)} style={styles.markReadBtn} activeOpacity={0.7}> Marcar lida )} @@ -164,28 +186,33 @@ export default function Profile() { )} - - {(isBarber ? [ - ['agenda_pessoal', 'Agenda Global'], - ['reviews', 'Avaliações'], - ['estatisticas', 'Resumo'], - ] : [ - ['agenda', 'Minha Agenda'], - ['favoritos', 'Favoritos'], - ['pedidos', 'Compras'], - ]).map(([id, label]) => ( - setActiveTab(id)} - style={[styles.tabItem, activeTab === id && styles.tabActive]} - > - {label} - - ))} - + {/* Dynamic Tab Bar */} + + + {(isBarber ? [ + ['agenda_pessoal', 'Agenda Global'], + ['reviews', 'Avaliações'], + ['estatisticas', 'Resumo'], + ] : [ + ['agenda', 'Minha Agenda'], + ['favoritos', 'Favoritos'], + ['pedidos', 'Compras'], + ]).map(([id, label]) => ( + setActiveTab(id)} + style={[styles.tabItem, activeTab === id && styles.tabActive]} + activeOpacity={0.8} + > + {label} + + ))} + + - {/* Conteúdo das Tabs */} + {/* Tab Content Area */} + {/* CLIENTE TABS */} {activeTab === 'agenda' && ( @@ -194,115 +221,236 @@ export default function Profile() { const shop = shops.find((s) => s.id === appt.shopId); const canReview = appt.status === 'concluido' && !reviewedAppointments.has(appt.id); const dateParts = appt.date.split(' '); + const day = dateParts[0].split('-')[2]; + const month = new Date(dateParts[0]).toLocaleString('pt-PT', { month: 'short' }).toUpperCase(); + return ( - - {dateParts[0].split('-')[2]} - {new Date(dateParts[0]).toLocaleString('pt-PT', { month: 'short' }).toUpperCase()} + {/* tear-off style calendar block */} + + + {day} + {month} + {shop?.name || 'Barbearia'} - {dateParts[1]} · {currency(appt.total)} + + + {dateParts[1]} • {currency(appt.total)} + - + + + {statusLabel[appt.status]} + {canReview && ( - )} ); }) - ) : Sem marcações agendadas.} + ) : ( + + + Sem marcações agendadas. + + )} )} {activeTab === 'favoritos' && ( - {favoriteShops.length > 0 ? favoriteShops.map(s => ( - navigation.navigate('ShopDetails', { shopId: s.id })}> - - {s.name.charAt(0)} - {s.name}{s.address} - ★ {s.rating.toFixed(1)} - - - )) : Ainda não tens favoritos.} + {favoriteShops.length > 0 ? ( + favoriteShops.map((s) => ( + navigation.navigate('ShopDetails', { shopId: s.id })} + activeOpacity={0.9} + > + + + {s.name.charAt(0).toUpperCase()} + + + {s.name} + + + + {s.address && s.address !== 'Endereço a definir' ? s.address : 'Morada por definir'} + + + + + + {(s.rating || 0).toFixed(1)} + + + + )) + ) : ( + + + Ainda não tens favoritos. + + )} )} {activeTab === 'pedidos' && ( - {myOrders.length > 0 ? myOrders.map(order => { - const shop = shops.find((s) => s.id === order.shopId); - return ( - - - - {shop?.name || 'Barbearia'} - {new Date(order.createdAt).toLocaleDateString('pt-PT')} · {order.items.length} item(s) + {myOrders.length > 0 ? ( + myOrders.map((order) => { + const shop = shops.find((s) => s.id === order.shopId); + return ( + + + + {shop?.name || 'Barbearia'} + + + + {new Date(order.createdAt).toLocaleDateString('pt-PT')} • {order.items.length} item(s) + + + + {currency(order.total)} - {currency(order.total)} - - - {statusLabel[order.status]} - - - ); - }) : Ainda não tens compras.} + + + {statusLabel[order.status]} + + + ); + }) + ) : ( + + + Ainda não tens compras. + + )} )} {/* BARBER TABS */} {activeTab === 'agenda_pessoal' && ( - Todas as marcações do espaço - {myAppointments.map(appt => ( - - - - {appt.date.split(' ')[0].split('-')[2]} - {new Date(appt.date.split(' ')[0]).toLocaleString('pt-PT', { month: 'short' }).toUpperCase()} - - - {appt.date.split(' ')[1]} - Status: {statusLabel[appt.status]} - - - - ))} + + + Todas as marcações do espaço + + + {myAppointments.length > 0 ? ( + myAppointments.map((appt) => { + const dateParts = appt.date.split(' '); + const day = dateParts[0].split('-')[2]; + const month = new Date(dateParts[0]).toLocaleString('pt-PT', { month: 'short' }).toUpperCase(); + + return ( + + + + + {day} + {month} + + + {dateParts[1]} + + + Total: {currency(appt.total)} + + + + + {statusLabel[appt.status]} + + + + ); + }) + ) : ( + + + Sem marcações registadas. + + )} )} {activeTab === 'reviews' && ( - {shopReviews.map(r => ( - - - {'★'.repeat(r.rating)} - {new Date(r.created_at).toLocaleDateString()} - - {r.comment || 'Sem comentário.'} - - ))} + {shopReviews.length > 0 ? ( + shopReviews.map((r) => ( + + + + {[1, 2, 3, 4, 5].map((star) => ( + + ))} + + {new Date(r.created_at).toLocaleDateString()} + + {r.comment || 'Sem comentário escrito.'} + + )) + ) : ( + + + Ainda não há avaliações neste espaço. + + )} )} {activeTab === 'estatisticas' && ( + + + Total de Marcações {myAppointments.length} + + + Rating Médio - {myShop?.rating.toFixed(1)} ★ + {(myShop?.rating || 0).toFixed(1)} ★ + + + Vendas em Produtos {currency(myOrders.reduce((sum, o) => sum + o.total, 0))} @@ -310,31 +458,44 @@ export default function Profile() { )} - {/* Review Modal for Clients */} + {/* Review Modal Dialog */} {reviewTarget && ( Avaliar {reviewTarget.shopName} - {[1,2,3,4,5].map(s => ( - setRating(s)}> - = s && styles.starActive]}>★ + {[1, 2, 3, 4, 5].map((s) => ( + setRating(s)} activeOpacity={0.7}> + = s ? "star" : "star-outline"} + size={36} + color={colors.star} + style={styles.starIconSpacing} + /> ))} - + - + )} + {/* Logout Section with Premium Styling */} - + Sair da Conta - + - Smart Agenda v1.0.4 + Smart Agenda v1.0.5 @@ -342,78 +503,521 @@ export default function Profile() { } const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: colors.background }, - content: { padding: 20, paddingBottom: 100 }, - profileHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 12, gap: 16, backgroundColor: colors.surfaceDark, borderRadius: radius.sm, padding: 18, ...shadows.card }, - avatarBox: { width: 64, height: 64, borderRadius: radius.lg, backgroundColor: colors.accentSoft, alignItems: 'center', justifyContent: 'center', borderWidth: 1, borderColor: 'rgba(255,255,255,0.16)' }, - avatarText: { color: '#7a4310', fontSize: 24, fontWeight: '900' }, - profileInfo: { flex: 1, gap: 2 }, - profileName: { color: colors.textInverse, fontSize: 22, fontWeight: '900' }, - profileEmail: { color: '#b7c4bf', fontSize: 14, fontWeight: '500' }, - roleBadge: { alignSelf: 'flex-start', backgroundColor: colors.primarySoft, borderRadius: radius.pill, paddingHorizontal: 10, paddingVertical: 4, marginTop: 6 }, - roleText: { color: colors.primaryDark, fontSize: 10, fontWeight: '800', textTransform: 'uppercase' }, - statsStrip: { flexDirection: 'row', gap: 10, marginBottom: 28 }, - profileStat: { flex: 1, backgroundColor: colors.surface, borderRadius: radius.sm, padding: 12, borderWidth: 1, borderColor: colors.border, ...shadows.soft }, - profileStatValue: { color: colors.text, fontSize: 18, fontWeight: '900' }, - profileStatLabel: { color: colors.textMuted, fontSize: 11, fontWeight: '800', textTransform: 'uppercase', marginTop: 2 }, - reviewActions: { flexDirection: 'row', gap: 14 }, - sectionTitle: { color: colors.text, fontSize: 18, fontWeight: '800', marginBottom: 12 }, - notifSection: { marginBottom: 32 }, - notifScroll: { marginHorizontal: -20, paddingHorizontal: 20 }, - notifCard: { backgroundColor: colors.surface, width: 240, borderRadius: radius.sm, padding: 16, marginRight: 12, borderWidth: 1, borderColor: colors.border, gap: 8, ...shadows.soft }, - notifUnread: { borderColor: colors.primary, backgroundColor: colors.primarySoft }, - notifMsg: { color: colors.textMuted, fontSize: 13, lineHeight: 18, fontWeight: '500' }, - markRead: { color: colors.primaryDark, fontSize: 12, fontWeight: '800', textTransform: 'uppercase', letterSpacing: 0 }, - tabBar: { flexDirection: 'row', gap: 8, paddingBottom: 18 }, - tabItem: { paddingVertical: 10, paddingHorizontal: 14, borderRadius: radius.pill, backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border }, - tabActive: { backgroundColor: colors.primary, borderColor: colors.primary }, - tabText: { color: colors.textMuted, fontSize: 15, fontWeight: '700' }, - tabTextActive: { color: colors.textInverse }, - tabContent: { minHeight: 200 }, - listContainer: { gap: 16 }, - emptyTxt: { color: colors.textMuted, textAlign: 'center', marginTop: 20, fontWeight: '600' }, - subTitle: { color: colors.textMuted, fontSize: 13, fontWeight: '700', textTransform: 'uppercase', marginBottom: 8, letterSpacing: 0 }, - agendaCard: { padding: 16, gap: 12, backgroundColor: colors.surface, borderRadius: radius.sm, borderWidth: 1, borderColor: colors.border }, - agendaTop: { flexDirection: 'row', alignItems: 'center', gap: 14 }, - dateBox: { backgroundColor: colors.primarySoft, padding: 10, borderRadius: radius.md, alignItems: 'center', minWidth: 55, borderWidth: 1, borderColor: colors.border }, - dateDay: { color: colors.primaryDark, fontSize: 20, fontWeight: '900' }, - dateMonth: { color: colors.primary, fontSize: 10, fontWeight: '800', textTransform: 'uppercase' }, - agendaMain: { flex: 1, gap: 3 }, - agendaShop: { color: colors.text, fontSize: 17, fontWeight: '800' }, - agendaTime: { color: colors.textMuted, fontSize: 13, fontWeight: '500' }, - statusTag: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: radius.pill }, - statusText: { fontSize: 10, fontWeight: '900', textTransform: 'uppercase', letterSpacing: 0 }, - reviewBtn: { marginTop: 8, borderRadius: radius.md }, - shopCard: { flexDirection: 'row', alignItems: 'center', gap: 14, padding: 16, backgroundColor: colors.surface, borderRadius: radius.sm, marginBottom: 4 }, - shopIcon: { width: 48, height: 48, borderRadius: radius.md, backgroundColor: colors.primarySoft, alignItems: 'center', justifyContent: 'center', borderWidth: 1, borderColor: colors.border }, - shopIconText: { color: colors.primary, fontSize: 20, fontWeight: '900' }, - shopName: { color: colors.text, fontSize: 16, fontWeight: '800' }, - shopAddr: { color: colors.textMuted, fontSize: 12, fontWeight: '500' }, - shopRating: { color: colors.star, fontWeight: '900', fontSize: 14 }, - orderCard: { padding: 16, gap: 12, backgroundColor: colors.surface, borderRadius: radius.sm, borderWidth: 1, borderColor: colors.border }, - orderTop: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 }, - orderShop: { color: colors.text, fontSize: 16, fontWeight: '900' }, - orderMeta: { color: colors.textMuted, fontSize: 12, fontWeight: '600', marginTop: 4 }, - orderTotal: { color: colors.primary, fontSize: 18, fontWeight: '900' }, - reviewCard: { padding: 18, gap: 10, backgroundColor: colors.surface, borderRadius: radius.sm }, - reviewHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, - reviewStars: { color: colors.star, fontSize: 15 }, - reviewDate: { color: colors.textSubtle, fontSize: 12, fontWeight: '600' }, - reviewComment: { color: colors.text, fontSize: 14, lineHeight: 20, fontWeight: '400' }, - statCard: { padding: 24, alignItems: 'center', gap: 6, backgroundColor: colors.surface, borderRadius: radius.sm, borderWidth: 1, borderColor: colors.border }, - statLabel: { color: colors.textMuted, fontSize: 12, fontWeight: '700', textTransform: 'uppercase', letterSpacing: 0 }, - statValue: { color: colors.text, fontSize: 28, fontWeight: '900' }, - reviewModal: { marginTop: 24, padding: 24, gap: 16, borderColor: colors.primary, borderRadius: radius.sm }, - reviewTitle: { color: colors.text, fontSize: 18, fontWeight: '800', textAlign: 'center' }, - stars: { flexDirection: 'row', justifyContent: 'center', gap: 12 }, - star: { fontSize: 36, color: colors.borderStrong }, - starActive: { color: colors.star }, - reviewInput: { backgroundColor: colors.surfaceMuted, borderRadius: radius.md, padding: 16, color: colors.text, height: 100, textAlignVertical: 'top', borderWidth: 1, borderColor: colors.border }, + container: { + flex: 1, + backgroundColor: colors.background, + }, + content: { + padding: 20, + paddingBottom: 40, + gap: 20, + }, + profileHeader: { + flexDirection: 'row', + alignItems: 'center', + gap: 16, + backgroundColor: colors.surfaceDark, + borderRadius: radius.lg, + padding: 20, + shadowColor: colors.primaryDark, + shadowOffset: { width: 0, height: 6 }, + shadowOpacity: 0.1, + shadowRadius: 12, + elevation: 4, + borderWidth: 1, + borderColor: 'rgba(255,255,255,0.04)', + marginTop: 6, + }, + avatarGradient: { + width: 62, + height: 62, + borderRadius: 31, + backgroundColor: colors.primary, + alignItems: 'center', + justifyContent: 'center', + borderWidth: 2, + borderColor: 'rgba(255,255,255,0.18)', + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + }, + avatarText: { + color: colors.textInverse, + fontSize: 22, + fontWeight: '900', + }, + profileInfo: { + flex: 1, + gap: 4, + }, + profileName: { + color: colors.textInverse, + fontSize: 20, + fontWeight: '900', + letterSpacing: -0.4, + }, + profileEmail: { + color: 'rgba(248,251,248,0.72)', + fontSize: 14, + fontWeight: '500', + }, + roleBadge: { + alignSelf: 'flex-start', + backgroundColor: colors.primarySoft, + borderRadius: radius.pill, + paddingHorizontal: 10, + paddingVertical: 4, + marginTop: 4, + flexDirection: 'row', + alignItems: 'center', + gap: 4, + }, + roleText: { + color: colors.primaryDark, + fontSize: 10, + fontWeight: '800', + textTransform: 'uppercase', + }, + statsStrip: { + flexDirection: 'row', + gap: 10, + }, + profileStat: { + flex: 1, + backgroundColor: colors.surface, + borderRadius: radius.lg, + padding: 14, + borderWidth: 1, + borderColor: 'rgba(15,118,110,0.06)', + alignItems: 'center', + shadowColor: '#102018', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.03, + shadowRadius: 8, + elevation: 2, + }, + profileStatValue: { + color: colors.text, + fontSize: 18, + fontWeight: '900', + }, + profileStatLabel: { + color: colors.textMuted, + fontSize: 10, + fontWeight: '700', + textTransform: 'uppercase', + marginTop: 2, + }, + sectionHeaderRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 6, + marginBottom: 10, + }, + sectionTitle: { + color: colors.text, + fontSize: 16, + fontWeight: '800', + }, + notifSection: { + marginTop: 4, + }, + notifScroll: { + marginHorizontal: -20, + paddingHorizontal: 20, + }, + notifCard: { + backgroundColor: colors.surface, + width: 240, + borderRadius: radius.md, + padding: 14, + marginRight: 12, + borderWidth: 1, + borderColor: 'rgba(15,118,110,0.06)', + gap: 6, + shadowColor: '#102018', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.02, + shadowRadius: 6, + elevation: 1, + }, + notifUnread: { + borderColor: 'rgba(15,118,110,0.2)', + backgroundColor: colors.primarySoft, + }, + notifHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + unreadDot: { + width: 6, + height: 6, + borderRadius: 3, + backgroundColor: colors.primary, + }, + notifMsg: { + color: colors.textMuted, + fontSize: 13, + lineHeight: 18, + fontWeight: '500', + }, + markReadBtn: { + alignSelf: 'flex-start', + marginTop: 2, + }, + markRead: { + color: colors.primaryDark, + fontSize: 10, + fontWeight: '800', + textTransform: 'uppercase', + letterSpacing: 0.3, + }, + tabBarContainer: { + marginVertical: 4, + }, + tabBar: { + flexDirection: 'row', + gap: 8, + }, + tabItem: { + paddingVertical: 10, + paddingHorizontal: 16, + borderRadius: radius.pill, + backgroundColor: colors.surface, + borderWidth: 1, + borderColor: 'rgba(15,118,110,0.06)', + }, + tabActive: { + backgroundColor: colors.primary, + borderColor: colors.primary, + }, + tabText: { + color: colors.textMuted, + fontSize: 14, + fontWeight: '700', + }, + tabTextActive: { + color: colors.textInverse, + }, + tabContent: { + minHeight: 200, + }, + listContainer: { + gap: 12, + }, + emptyBlock: { + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 40, + gap: 10, + }, + emptyTxt: { + color: colors.textMuted, + textAlign: 'center', + fontWeight: '600', + fontSize: 14, + }, + subTitleRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 6, + marginBottom: 4, + }, + subTitle: { + color: colors.text, + fontSize: 14, + fontWeight: '800', + }, + agendaCard: { + padding: 14, + backgroundColor: colors.surface, + borderRadius: radius.lg, + borderWidth: 1, + borderColor: 'rgba(15,118,110,0.06)', + shadowColor: '#102018', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.03, + shadowRadius: 8, + elevation: 2, + }, + agendaTop: { + flexDirection: 'row', + alignItems: 'center', + gap: 14, + }, + calendarBlock: { + backgroundColor: colors.surfaceMuted, + borderRadius: radius.md, + alignItems: 'center', + minWidth: 52, + borderWidth: 1, + borderColor: 'rgba(15,118,110,0.06)', + overflow: 'hidden', + }, + calendarHeader: { + height: 4, + backgroundColor: colors.primary, + width: '100%', + }, + dateDay: { + color: colors.text, + fontSize: 18, + fontWeight: '900', + marginTop: 4, + }, + dateMonth: { + color: colors.primary, + fontSize: 9, + fontWeight: '800', + textTransform: 'uppercase', + marginBottom: 4, + }, + agendaMain: { + flex: 1, + gap: 3, + }, + agendaShop: { + color: colors.text, + fontSize: 16, + fontWeight: '800', + }, + agendaMetaRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + }, + agendaTime: { + color: colors.textMuted, + fontSize: 13, + fontWeight: '500', + }, + statusTag: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + paddingHorizontal: 8, + paddingVertical: 4, + borderRadius: radius.pill, + }, + statusText: { + fontSize: 9, + fontWeight: '800', + textTransform: 'uppercase', + letterSpacing: 0.2, + }, + reviewBtn: { + marginTop: 10, + borderRadius: radius.md, + }, + shopCard: { + flexDirection: 'row', + alignItems: 'center', + gap: 12, + padding: 12, + backgroundColor: colors.surface, + borderRadius: radius.lg, + borderWidth: 1, + borderColor: 'rgba(15,118,110,0.06)', + shadowColor: '#102018', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.03, + shadowRadius: 8, + elevation: 2, + }, + shopIcon: { + width: 44, + height: 44, + borderRadius: radius.md, + backgroundColor: colors.primarySoft, + alignItems: 'center', + justifyContent: 'center', + borderWidth: 0.5, + borderColor: 'rgba(15,118,110,0.1)', + }, + shopIconText: { + color: colors.primary, + fontSize: 18, + fontWeight: '900', + }, + shopCardBody: { + flex: 1, + gap: 2, + }, + shopName: { + color: colors.text, + fontSize: 15, + fontWeight: '800', + }, + shopLocationRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + }, + shopAddr: { + color: colors.textMuted, + fontSize: 12, + fontWeight: '500', + flex: 1, + }, + shopRatingBadge: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.accentSoft, + borderRadius: radius.pill, + paddingHorizontal: 8, + paddingVertical: 3, + gap: 3, + }, + shopRatingText: { + color: '#7a4310', + fontWeight: '800', + fontSize: 11, + }, + orderCard: { + padding: 14, + gap: 10, + backgroundColor: colors.surface, + borderRadius: radius.lg, + borderWidth: 1, + borderColor: 'rgba(15,118,110,0.06)', + shadowColor: '#102018', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.03, + shadowRadius: 8, + elevation: 2, + }, + orderTop: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'flex-start', + gap: 12, + }, + orderShop: { + color: colors.text, + fontSize: 16, + fontWeight: '800', + }, + orderMetaRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + }, + orderMeta: { + color: colors.textMuted, + fontSize: 12, + fontWeight: '500', + }, + orderTotal: { + color: colors.primary, + fontSize: 16, + fontWeight: '900', + }, + reviewCard: { + padding: 16, + gap: 8, + backgroundColor: colors.surface, + borderRadius: radius.lg, + borderWidth: 1, + borderColor: 'rgba(15,118,110,0.06)', + }, + reviewHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + reviewStarsRow: { + flexDirection: 'row', + gap: 2, + }, + reviewDate: { + color: colors.textSubtle, + fontSize: 12, + fontWeight: '500', + }, + reviewComment: { + color: colors.text, + fontSize: 14, + lineHeight: 18, + fontWeight: '400', + }, + statCard: { + padding: 20, + alignItems: 'center', + gap: 6, + backgroundColor: colors.surface, + borderRadius: radius.lg, + borderWidth: 1, + borderColor: 'rgba(15,118,110,0.06)', + shadowColor: '#102018', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.03, + shadowRadius: 8, + elevation: 2, + }, + statIconContainer: { + width: 40, + height: 40, + borderRadius: 20, + backgroundColor: colors.surfaceMuted, + alignItems: 'center', + justifyContent: 'center', + marginBottom: 4, + borderWidth: 1, + borderColor: 'rgba(15,118,110,0.04)', + }, + statLabel: { + color: colors.textMuted, + fontSize: 12, + fontWeight: '700', + textTransform: 'uppercase', + }, + statValue: { + color: colors.text, + fontSize: 24, + fontWeight: '900', + }, + reviewModal: { + marginTop: 10, + padding: 20, + gap: 14, + borderColor: colors.primary, + borderRadius: radius.lg, + }, + reviewTitle: { + color: colors.text, + fontSize: 16, + fontWeight: '800', + textAlign: 'center', + }, + stars: { + flexDirection: 'row', + justifyContent: 'center', + gap: 10, + }, + starIconSpacing: { + paddingHorizontal: 2, + }, + reviewInput: { + backgroundColor: colors.surfaceMuted, + borderRadius: radius.md, + padding: 12, + color: colors.text, + height: 90, + textAlignVertical: 'top', + borderWidth: 1, + borderColor: 'rgba(15,118,110,0.08)', + }, + reviewActions: { + flexDirection: 'row', + gap: 12, + }, logoutSection: { - marginTop: 60, - paddingTop: 30, + marginTop: 20, + paddingTop: 20, borderTopWidth: 1, - borderTopColor: colors.border, + borderTopColor: 'rgba(15,118,110,0.08)', alignItems: 'center', }, logoutButtonPremium: { @@ -421,27 +1025,27 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', backgroundColor: colors.dangerSoft, - paddingVertical: 18, - paddingHorizontal: 24, - borderRadius: radius.sm, + paddingVertical: 14, + paddingHorizontal: 20, + borderRadius: radius.md, width: '100%', borderWidth: 1, - borderColor: '#fecaca', - gap: 12, + borderColor: 'rgba(239,68,68,0.18)', + gap: 10, + shadowColor: colors.danger, + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.02, + shadowRadius: 6, }, logoutButtonText: { color: colors.danger, - fontSize: 16, + fontSize: 15, fontWeight: '800', }, - logoutButtonIcon: { - color: colors.danger, - fontSize: 20, - }, versionText: { color: colors.textSubtle, - fontSize: 12, - marginTop: 16, + fontSize: 11, + marginTop: 12, fontWeight: '600', }, }); diff --git a/src/pages/ShopDetails.tsx b/src/pages/ShopDetails.tsx index 0612190..8080fb3 100644 --- a/src/pages/ShopDetails.tsx +++ b/src/pages/ShopDetails.tsx @@ -9,6 +9,7 @@ import { Button } from '../components/ui/Button'; import { currency } from '../lib/format'; import { RootStackParamList } from '../navigation/types'; import { colors, radius, shadows } from '../lib/theme'; +import { Ionicons } from '@expo/vector-icons'; type Tab = 'servicos' | 'barbeiros' | 'produtos' | 'detalhes'; @@ -34,6 +35,7 @@ export default function ShopDetails() { return ( + Carregando... @@ -44,6 +46,7 @@ export default function ShopDetails() { return ( + Não encontrado @@ -69,62 +72,97 @@ export default function ShopDetails() { const schedule = shop.schedule || defaultSchedule; const currentDayIndex = new Date().getDay() === 0 ? 6 : new Date().getDay() - 1; + const isFav = isFavorite(shop.id); return ( - + + + {/* Cover Hero Banner */} {shop.imageUrl ? ( ) : ( - 💈 + + + )} - - navigation.goBack()} style={styles.backBtn}> - + + navigation.goBack()} style={styles.backBtn} activeOpacity={0.8}> + - toggleFavorite(shop.id)} style={styles.favBtn}> - - {isFavorite(shop.id) ? '♥' : '♡'} - + toggleFavorite(shop.id)} style={styles.favBtn} activeOpacity={0.8}> + + {/* Overlapping Info Summary Sheet */} - - ★ {shop.rating.toFixed(1)} + + + {(shop.rating || 0).toFixed(1)} + + + {shop.services.length} serviços - {shop.services.length} serviços + {shop.name} - - 📍 {shop.address} + + + + + {shop.address && shop.address !== 'Endereço a definir' ? shop.address : 'Endereço por definir'} + + + {/* Quick Stats Grid */} - {shop.barbers.length} - Equipa + + + {shop.barbers.length} + Equipa + - {shop.products.length} - Produtos + + + {shop.products.length} + Produtos + - {schedule[currentDayIndex]?.closed ? 'Fechado' : 'Aberto'} - Hoje + + + + {schedule[currentDayIndex]?.closed ? 'Fechado' : 'Aberto'} + + Hoje + - + {/* Tab Selector section */} {[ @@ -137,6 +175,7 @@ export default function ShopDetails() { key={id} onPress={() => setTab(id as Tab)} style={[styles.tabItem, tab === id && styles.tabItemActive]} + activeOpacity={0.8} > {label} @@ -144,71 +183,135 @@ export default function ShopDetails() { - {/* Listagem de Conteúdo */} + {/* Content Tabs Area */} {tab === 'servicos' && ( - {shop.services.map((s) => ( - - - {s.name} - {s.duration} min · {currency(s.price)} - - - - ))} + {shop.services.length > 0 ? ( + shop.services.map((s) => ( + + + {s.name} + + + {s.duration} min + + {currency(s.price)} + + + + + )) + ) : ( + + + Sem serviços disponíveis de momento. + + )} )} {tab === 'barbeiros' && ( - {shop.barbers.map((b) => ( - - - {b.name.charAt(0)} - - - {b.name} - {b.specialties.join(', ')} - - - ))} + {shop.barbers.length > 0 ? ( + shop.barbers.map((b) => ( + + + {b.name.charAt(0).toUpperCase()} + + + {b.name} + + {b.specialties.length > 0 ? b.specialties.join(', ') : 'Barbeiro especialista'} + + + + + + + )) + ) : ( + + + Sem profissionais disponíveis de momento. + + )} )} {tab === 'produtos' && ( - {shop.products.map((p) => ( - - - {p.name} - {currency(p.price)} - - {p.stock} em stock - - - ))} + {shop.products.length > 0 ? ( + shop.products.map((p) => ( + + + {p.name} + {currency(p.price)} + + + + + {p.stock > 0 ? `${p.stock} em stock` : 'Esgotado'} + + + + + + )) + ) : ( + + + Sem produtos disponíveis de momento. + + )} )} {tab === 'detalhes' && ( - Horário - {schedule.map((s, idx) => ( - - {s.day} - - {s.closed ? 'Fechado' : `${s.open} - ${s.close}`} - - - ))} + + + Horário de Funcionamento + + + + {schedule.map((s, idx) => ( + + + {s.day} {idx === currentDayIndex && '• Hoje'} + + + {s.closed ? 'Fechado' : `${s.open} - ${s.close}`} + + + ))} + + - Contacto - Linking.openURL(`tel:${shop.contacts?.phone1}`)}> - {shop.contacts?.phone1 || 'Não disponível'} - + + + + Contacto Telefónico + + + {shop.contacts?.phone1 ? ( + Linking.openURL(`tel:${shop.contacts?.phone1}`)} style={styles.phoneLinkBox} activeOpacity={0.7}> + + {shop.contacts?.phone1} + + ) : ( + Contacto telefónico não disponível. + )} )} @@ -226,7 +329,7 @@ const styles = StyleSheet.create({ paddingBottom: 40, }, hero: { - height: 230, + height: 250, position: 'relative', }, heroImage: { @@ -240,12 +343,9 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', }, - heroPlaceholderText: { - fontSize: 64, - }, heroOverlay: { ...StyleSheet.absoluteFillObject, - backgroundColor: colors.overlay, + backgroundColor: 'rgba(12,20,17,0.3)', }, heroHeader: { position: 'absolute', @@ -258,116 +358,135 @@ const styles = StyleSheet.create({ paddingTop: 10, }, backBtn: { - width: 44, - height: 44, - borderRadius: radius.pill, + width: 42, + height: 42, + borderRadius: 21, backgroundColor: 'rgba(20,33,29,0.72)', alignItems: 'center', justifyContent: 'center', - borderWidth: 1, + borderWidth: 0.5, borderColor: 'rgba(255,255,255,0.16)', }, - backIcon: { - color: colors.textInverse, - fontSize: 20, - fontWeight: '700', - }, favBtn: { - width: 44, - height: 44, - borderRadius: radius.pill, + width: 42, + height: 42, + borderRadius: 21, backgroundColor: 'rgba(20,33,29,0.72)', alignItems: 'center', justifyContent: 'center', - borderWidth: 1, + borderWidth: 0.5, borderColor: 'rgba(255,255,255,0.16)', }, - favIcon: { - color: colors.textInverse, - fontSize: 22, - }, - favActive: { - color: colors.danger, - }, summaryWrap: { paddingHorizontal: 20, - marginTop: -36, + marginTop: -46, zIndex: 2, }, summaryCard: { backgroundColor: colors.surface, - borderRadius: radius.sm, - padding: 18, - gap: 12, + borderRadius: radius.lg, + padding: 20, + gap: 14, borderWidth: 1, - borderColor: colors.border, - ...shadows.card, + borderColor: 'rgba(15,118,110,0.06)', + shadowColor: '#102018', + shadowOffset: { width: 0, height: 8 }, + shadowOpacity: 0.08, + shadowRadius: 16, + elevation: 4, }, summaryTop: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, - summaryMeta: { - color: colors.textMuted, - fontSize: 12, - fontWeight: '800', - textTransform: 'uppercase', - }, - ratingBox: { - alignSelf: 'flex-start', - backgroundColor: colors.accent, + ratingBadge: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.accentSoft, borderRadius: radius.pill, paddingHorizontal: 10, paddingVertical: 4, + gap: 4, }, ratingValue: { - color: colors.textInverse, + color: '#7a4310', fontSize: 12, fontWeight: '900', }, + servicesCountBadge: { + backgroundColor: colors.primarySoft, + paddingHorizontal: 10, + paddingVertical: 4, + borderRadius: radius.pill, + }, + summaryMeta: { + color: colors.primaryDark, + fontSize: 11, + fontWeight: '800', + textTransform: 'uppercase', + }, shopName: { color: colors.text, - fontSize: 28, - lineHeight: 32, + fontSize: 26, fontWeight: '900', + letterSpacing: -0.5, }, addrBox: { - opacity: 0.8, + flexDirection: 'row', + alignItems: 'center', + gap: 6, }, shopAddr: { + flex: 1, color: colors.textMuted, fontSize: 14, fontWeight: '500', }, quickStats: { flexDirection: 'row', - gap: 10, + gap: 8, + marginTop: 2, }, quickStat: { flex: 1, + flexDirection: 'row', + alignItems: 'center', + gap: 8, backgroundColor: colors.surfaceMuted, - borderRadius: radius.sm, + borderRadius: radius.md, padding: 10, borderWidth: 1, - borderColor: colors.border, + borderColor: 'rgba(15,118,110,0.05)', }, quickValue: { color: colors.text, - fontSize: 16, + fontSize: 14, fontWeight: '900', }, + closedText: { + color: colors.danger, + }, quickLabel: { color: colors.textMuted, - fontSize: 11, + fontSize: 10, fontWeight: '700', - marginTop: 2, textTransform: 'uppercase', + marginTop: 1, + }, + reserveBtn: { + marginTop: 4, + borderRadius: radius.md, + shadowColor: colors.primary, + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.1, + shadowRadius: 8, + elevation: 3, }, tabSection: { backgroundColor: colors.background, - paddingVertical: 10, - marginTop: 6, + paddingVertical: 12, + marginTop: 8, }, tabsScroll: { paddingHorizontal: 20, @@ -375,11 +494,11 @@ const styles = StyleSheet.create({ }, tabItem: { paddingVertical: 10, - paddingHorizontal: 14, + paddingHorizontal: 16, borderRadius: radius.pill, - backgroundColor: colors.surfaceMuted, + backgroundColor: colors.surface, borderWidth: 1, - borderColor: colors.border, + borderColor: 'rgba(15,118,110,0.05)', }, tabItemActive: { backgroundColor: colors.primary, @@ -394,7 +513,7 @@ const styles = StyleSheet.create({ color: colors.textInverse, }, contentArea: { - padding: 20, + paddingHorizontal: 20, }, grid: { gap: 12, @@ -404,19 +523,48 @@ const styles = StyleSheet.create({ alignItems: 'center', padding: 16, gap: 12, + borderRadius: radius.md, + borderWidth: 1, + borderColor: 'rgba(15,118,110,0.06)', + backgroundColor: colors.surface, + shadowColor: '#102018', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.02, + shadowRadius: 6, + elevation: 1, }, svcInfo: { flex: 1, - gap: 4, + gap: 6, }, svcName: { color: colors.text, fontSize: 16, fontWeight: '800', }, + svcMetaRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + }, svcMeta: { color: colors.textMuted, fontSize: 13, + fontWeight: '500', + }, + bulletSeparator: { + color: colors.borderStrong, + fontSize: 12, + marginHorizontal: 2, + }, + svcPrice: { + color: colors.primary, + fontSize: 14, + fontWeight: '800', + }, + svcBookBtn: { + borderRadius: radius.md, + paddingHorizontal: 14, }, barberList: { gap: 12, @@ -426,20 +574,30 @@ const styles = StyleSheet.create({ alignItems: 'center', padding: 14, gap: 14, + borderRadius: radius.md, + borderWidth: 1, + borderColor: 'rgba(15,118,110,0.06)', + backgroundColor: colors.surface, }, barberAvatar: { - width: 50, - height: 50, - borderRadius: radius.md, + width: 48, + height: 48, + borderRadius: 24, backgroundColor: colors.primarySoft, alignItems: 'center', justifyContent: 'center', + borderWidth: 1, + borderColor: 'rgba(15,118,110,0.1)', }, avatarTxt: { color: colors.primary, - fontSize: 20, + fontSize: 18, fontWeight: '900', }, + barberInfo: { + flex: 1, + gap: 2, + }, barberName: { color: colors.text, fontSize: 16, @@ -448,15 +606,29 @@ const styles = StyleSheet.create({ barberSpecs: { color: colors.textMuted, fontSize: 12, + fontWeight: '500', + }, + barberBadge: { + width: 24, + height: 24, + borderRadius: 12, + backgroundColor: colors.primarySoft, + alignItems: 'center', + justifyContent: 'center', }, productCard: { padding: 16, - gap: 8, + gap: 10, + borderRadius: radius.md, + borderWidth: 1, + borderColor: 'rgba(15,118,110,0.06)', + backgroundColor: colors.surface, }, prodHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', + gap: 12, }, prodName: { color: colors.text, @@ -469,28 +641,69 @@ const styles = StyleSheet.create({ fontSize: 16, fontWeight: '900', }, - prodStock: { - color: colors.textMuted, - fontSize: 12, - marginBottom: 4, + productMetaRow: { + flexDirection: 'row', + alignItems: 'center', + }, + stockBadge: { + backgroundColor: colors.successSoft, + paddingHorizontal: 8, + paddingVertical: 3, + borderRadius: radius.xs, + }, + outOfStockBadge: { + backgroundColor: colors.dangerSoft, + }, + stockText: { + color: colors.success, + fontSize: 10, + fontWeight: '800', + textTransform: 'uppercase', + }, + outOfStockText: { + color: colors.danger, + }, + productBtn: { + borderRadius: radius.md, + marginTop: 2, }, detailsBox: { padding: 20, - gap: 12, + gap: 14, + borderRadius: radius.lg, + borderWidth: 1, + borderColor: 'rgba(15,118,110,0.06)', + backgroundColor: colors.surface, + }, + detailTitleRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 8, + marginBottom: 4, }, detailTitle: { color: colors.text, - fontSize: 18, + fontSize: 16, fontWeight: '800', - marginBottom: 8, + }, + scheduleList: { + gap: 8, }, schedRow: { flexDirection: 'row', justifyContent: 'space-between', + paddingVertical: 4, + paddingHorizontal: 8, + }, + schedRowToday: { + backgroundColor: colors.primarySoft, + borderRadius: radius.sm, + paddingVertical: 6, }, schedDay: { color: colors.textMuted, fontSize: 14, + fontWeight: '500', }, schedTime: { color: colors.text, @@ -498,28 +711,61 @@ const styles = StyleSheet.create({ fontWeight: '600', }, today: { - color: colors.primary, + color: colors.primaryDark, fontWeight: '800', }, divider: { height: 1, - backgroundColor: colors.border, - marginVertical: 12, + backgroundColor: 'rgba(15,118,110,0.08)', + marginVertical: 6, + }, + phoneLinkBox: { + flexDirection: 'row', + alignItems: 'center', + gap: 8, + backgroundColor: colors.primarySoft, + alignSelf: 'flex-start', + paddingVertical: 8, + paddingHorizontal: 14, + borderRadius: radius.sm, + borderWidth: 0.5, + borderColor: 'rgba(15,118,110,0.1)', }, contactLink: { color: colors.primaryDark, - fontSize: 16, - fontWeight: '700', + fontSize: 15, + fontWeight: '800', + }, + noContactText: { + color: colors.textSubtle, + fontSize: 14, + fontWeight: '500', }, centerState: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 40, + gap: 12, }, centerTitle: { color: colors.text, - fontSize: 18, + fontSize: 16, fontWeight: '800', }, + loadingSpinner: { + marginBottom: 4, + }, + emptyContentBlock: { + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 40, + gap: 10, + }, + emptyContentText: { + color: colors.textMuted, + fontSize: 14, + fontWeight: '600', + textAlign: 'center', + }, });