Files
Trabalho-da-escola/src/screens/BarberDetailScreen.tsx
Rodrigo Nogueira de Sousa 47d2cc65c3 first commit
2026-05-22 11:12:19 +01:00

471 lines
12 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import {
View,
Text,
ScrollView,
StyleSheet,
Pressable,
Image,
Alert,
FlatList,
} from 'react-native';
import { useRoute, useNavigation } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { RootStackParamList } from '../navigation/types';
import { supabase, isSupabaseConfigured } from '../services/supabase';
import { COLORS, SIZES, FONTS, SHADOWS } from '../constants/theme';
import { Barber, Review, Booking } from '../types';
import ReviewCard from '../components/ReviewCard';
import BookingCard from '../components/BookingCard';
const BarberDetailScreen: React.FC = () => {
const route = useRoute();
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>();
const { barberId } = route.params as { barberId: string };
const [barber, setBarber] = useState<Barber | null>(null);
const [reviews, setReviews] = useState<Review[]>([]);
const [bookings, setBookings] = useState<Booking[]>([]);
const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState<'reviews' | 'availability'>('reviews');
useEffect(() => {
loadBarberDetails();
}, [barberId]);
const loadBarberDetails = async () => {
try {
if (!isSupabaseConfigured) {
// Demo mode - use mock data
const { mockBarbers, mockReviews } = await import('../data/mockData');
const barber = mockBarbers.find(b => b.id === barberId);
if (barber) {
setBarber(barber);
// Load barber-specific reviews
const barberReviews = mockReviews.filter(r => r.barber_id === barberId);
setReviews(barberReviews);
// Mock bookings for demo
setBookings([]);
}
setLoading(false);
return;
}
const [barberRes, reviewsRes, bookingsRes] = await Promise.all([
supabase
.from('barbers')
.select(`
*,
user:users(name, photo, email, phone)
`)
.eq('id', barberId)
.single(),
supabase
.from('reviews')
.select(`
*,
user:users(name, photo)
`)
.eq('barber_id', barberId)
.order('created_at', { ascending: false }),
supabase
.from('bookings')
.select(`
*,
service:services(name, price),
customer:users(name)
`)
.eq('barber_id', barberId)
.eq('status', 'completed')
.order('booking_date', { ascending: false })
.limit(10)
]);
if (barberRes.data) setBarber(barberRes.data);
if (reviewsRes.data) setReviews(reviewsRes.data);
if (bookingsRes.data) setBookings(bookingsRes.data);
} catch (error) {
console.error('Erro ao carregar detalhes do barbeiro:', error);
Alert.alert('Erro', 'Falha ao carregar detalhes do barbeiro');
} finally {
setLoading(false);
}
};
const handleBookNow = () => {
Alert.alert(
'👨‍💼 Marcar com Barbeiro',
`Vou marcar com: ${barber?.user?.name}`,
[
{
text: 'Cancelar',
style: 'cancel',
},
{
text: 'Confirmar',
onPress: () => {
Alert.alert(
'✅ Barbeiro Selecionado',
`${barber?.user?.name} foi selecionado para a sua marcação.\n\nContinue para escolher serviço, data e horário.`,
[
{
text: 'OK',
onPress: () => navigation.navigate('Booking'),
},
]
);
},
},
]
);
};
const renderStars = (rating: number) => {
const stars = [];
const fullStars = Math.floor(rating);
for (let i = 0; i < fullStars; i++) {
stars.push(
<Text key={i} style={styles.star}></Text>
);
}
const emptyStars = 5 - fullStars;
for (let i = 0; i < emptyStars; i++) {
stars.push(
<Text key={`empty-${i}`} style={[styles.star, styles.emptyStar]}></Text>
);
}
return stars;
};
const renderAvailability = () => {
if (!barber?.availability) return null;
const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
return (
<View style={styles.availabilityContainer}>
{days.map((day) => {
const timeSlots = barber.availability[day.toLowerCase()] || [];
return (
<View key={day} style={styles.dayRow}>
<Text style={styles.dayText}>{day}</Text>
<View style={styles.timeSlots}>
{timeSlots.length > 0 ? (
timeSlots.map((time, index) => (
<Text key={index} style={styles.timeSlot}>
{time}
</Text>
))
) : (
<Text style={styles.closedText}>Fechado</Text>
)}
</View>
</View>
);
})}
</View>
);
};
if (loading) {
return (
<View style={styles.loadingContainer}>
<Text style={styles.loadingText}>A carregar...</Text>
</View>
);
}
if (!barber) {
return (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>Barbeiro não encontrado</Text>
</View>
);
}
return (
<ScrollView style={styles.container}>
{/* Barber Header */}
<View style={styles.header}>
<Image
source={{ uri: barber.user?.photo || 'https://via.placeholder.com/150' }}
style={styles.avatar}
/>
<View style={styles.barberInfo}>
<Text style={styles.name}>{barber.user?.name}</Text>
<Text style={styles.specialty}>{barber.specialty}</Text>
<View style={styles.ratingContainer}>
<View style={styles.stars}>
{renderStars(barber.rating)}
</View>
<Text style={styles.rating}>{barber.rating.toFixed(1)}</Text>
</View>
</View>
</View>
{/* Bio */}
<View style={styles.bioSection}>
<Text style={styles.sectionTitle}>Sobre</Text>
<Text style={styles.bio}>{barber.bio}</Text>
</View>
{/* Contact Info */}
<View style={styles.contactSection}>
<Text style={styles.sectionTitle}>Contacto</Text>
<Text style={styles.contactText}>📧 {barber.user?.email}</Text>
<Text style={styles.contactText}>📱 {barber.user?.phone}</Text>
</View>
{/* Book Button */}
<Pressable
style={({ pressed }) => [styles.bookButton, pressed && styles.bookButtonPressed]}
onPress={handleBookNow}
>
<Text style={styles.bookButtonText}>Marcar Horário</Text>
</Pressable>
{/* Tabs */}
<View style={styles.tabsContainer}>
<Pressable
style={({ pressed }) => [
styles.tab,
activeTab === 'reviews' && styles.activeTab,
pressed && styles.tabPressed
]}
onPress={() => setActiveTab('reviews')}
>
<Text style={[styles.tabText, activeTab === 'reviews' && styles.activeTabText]}>
Avaliações ({reviews.length})
</Text>
</Pressable>
<Pressable
style={({ pressed }) => [
styles.tab,
activeTab === 'availability' && styles.activeTab,
pressed && styles.tabPressed
]}
onPress={() => setActiveTab('availability')}
>
<Text style={[styles.tabText, activeTab === 'availability' && styles.activeTabText]}>
Disponibilidade
</Text>
</Pressable>
</View>
{/* Tab Content */}
<View style={styles.tabContent}>
{activeTab === 'reviews' ? (
<>
{reviews.length === 0 ? (
<Text style={styles.noReviewsText}>Sem avaliações</Text>
) : (
reviews.map((review) => (
<ReviewCard key={review.id} review={review} />
))
)}
</>
) : (
renderAvailability()
)}
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: COLORS.background,
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
loadingText: {
...FONTS.body,
color: COLORS.textSecondary,
},
errorContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
errorText: {
...FONTS.body,
color: COLORS.error,
},
header: {
flexDirection: 'row',
padding: SIZES.padding,
backgroundColor: COLORS.surface,
borderBottomWidth: 1,
borderBottomColor: COLORS.border,
},
avatar: {
width: 100,
height: 100,
borderRadius: 50,
marginRight: SIZES.margin,
},
barberInfo: {
flex: 1,
},
name: {
...FONTS.h1,
color: COLORS.text,
marginBottom: SIZES.base / 2,
},
specialty: {
...FONTS.h3,
color: COLORS.primary,
marginBottom: SIZES.base,
},
ratingContainer: {
flexDirection: 'row',
alignItems: 'center',
},
stars: {
flexDirection: 'row',
marginRight: SIZES.base,
},
star: {
color: COLORS.primary,
fontSize: 18,
},
emptyStar: {
color: COLORS.border,
},
rating: {
...FONTS.h3,
color: COLORS.text,
},
bioSection: {
padding: SIZES.padding,
},
sectionTitle: {
...FONTS.h2,
color: COLORS.text,
marginBottom: SIZES.margin,
},
bio: {
...FONTS.body,
color: COLORS.textSecondary,
lineHeight: 24,
},
contactSection: {
padding: SIZES.padding,
backgroundColor: COLORS.surface,
margin: SIZES.margin,
borderRadius: SIZES.radius,
...SHADOWS.light,
},
contactText: {
...FONTS.body,
color: COLORS.text,
marginBottom: SIZES.base,
},
bookButton: {
backgroundColor: COLORS.primary,
borderRadius: SIZES.radius,
padding: SIZES.padding,
alignItems: 'center',
margin: SIZES.margin,
...SHADOWS.medium,
cursor: 'pointer',
},
bookButtonPressed: {
opacity: 0.9,
},
bookButtonText: {
...FONTS.h3,
color: COLORS.background,
fontWeight: 'bold',
},
tabsContainer: {
flexDirection: 'row',
marginHorizontal: SIZES.margin,
marginBottom: SIZES.margin,
backgroundColor: COLORS.surface,
borderRadius: SIZES.radius,
padding: SIZES.base,
...SHADOWS.light,
},
tab: {
flex: 1,
paddingVertical: SIZES.base,
alignItems: 'center',
borderRadius: SIZES.base,
cursor: 'pointer',
},
tabPressed: {
opacity: 0.7,
},
activeTab: {
backgroundColor: COLORS.primary,
},
tabText: {
...FONTS.body,
color: COLORS.textSecondary,
},
activeTabText: {
color: COLORS.background,
fontWeight: 'bold',
},
tabContent: {
marginHorizontal: SIZES.margin,
marginBottom: SIZES.margin * 2,
},
noReviewsText: {
...FONTS.body,
color: COLORS.textSecondary,
textAlign: 'center',
paddingVertical: SIZES.padding,
},
availabilityContainer: {
backgroundColor: COLORS.surface,
borderRadius: SIZES.radius,
padding: SIZES.padding,
...SHADOWS.light,
},
dayRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: SIZES.base,
borderBottomWidth: 1,
borderBottomColor: COLORS.border,
},
dayText: {
...FONTS.body,
color: COLORS.text,
flex: 1,
},
timeSlots: {
flex: 2,
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'flex-end',
},
timeSlot: {
...FONTS.caption,
color: COLORS.primary,
backgroundColor: COLORS.background,
paddingHorizontal: SIZES.base,
paddingVertical: SIZES.base / 2,
borderRadius: SIZES.base / 2,
marginLeft: SIZES.base / 2,
marginBottom: SIZES.base / 2,
},
closedText: {
...FONTS.caption,
color: COLORS.textSecondary,
fontStyle: 'italic',
},
});
export default BarberDetailScreen;