From a657308f9d32d432d2d17e6bd35179da043b594d Mon Sep 17 00:00:00 2001 From: 230417 <230417@epvc.pt> Date: Tue, 12 May 2026 16:55:52 +0100 Subject: [PATCH] refactor: implement role-based profile dashboard with custom tab navigation and barber-specific views --- src/context/AppContext.tsx | 21 +- src/pages/Booking.tsx | 2 +- src/pages/Dashboard.tsx | 223 ++++++++++++-- src/pages/Profile.tsx | 589 ++++++++++--------------------------- 4 files changed, 380 insertions(+), 455 deletions(-) diff --git a/src/context/AppContext.tsx b/src/context/AppContext.tsx index e2f5245..b50982a 100644 --- a/src/context/AppContext.tsx +++ b/src/context/AppContext.tsx @@ -69,19 +69,24 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { useEffect(() => { let mounted = true; - storage.get<{ favorites?: string[]; cart?: CartItem[] }>('smart-agenda-mobile-state', {}).then((stored) => { - if (!mounted) return; - if (Array.isArray(stored.favorites)) setFavorites(stored.favorites); - if (Array.isArray(stored.cart)) setCart(stored.cart); - }); + if (user?.id) { + storage.get<{ favorites?: string[]; cart?: CartItem[] }>(`smart-agenda-user-${user.id}`, {}).then((stored) => { + if (!mounted) return; + setFavorites(Array.isArray(stored.favorites) ? stored.favorites : []); + }); + } else { + setFavorites([]); + } return () => { mounted = false; }; - }, []); + }, [user?.id]); useEffect(() => { - storage.set('smart-agenda-mobile-state', { favorites, cart }); - }, [favorites, cart]); + if (user?.id) { + storage.set(`smart-agenda-user-${user.id}`, { favorites, cart }); + } + }, [favorites, cart, user?.id]); const applySupabaseUser = async (authUser: any): Promise => { if (!authUser) return undefined; diff --git a/src/pages/Booking.tsx b/src/pages/Booking.tsx index 926e285..061beaf 100644 --- a/src/pages/Booking.tsx +++ b/src/pages/Booking.tsx @@ -62,7 +62,7 @@ export default function Booking() { }); if (appt) { Alert.alert('Sucesso', 'O seu agendamento foi confirmado.'); - navigation.navigate('Profile' as never); + navigation.navigate('ProfileTab' as never); } }; diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 603410a..851c08d 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -31,18 +31,13 @@ export default function Dashboard() { user, shops, appointments, - orders, refreshShops, updateAppointmentStatus, - updateOrderStatus, addService, - updateService, deleteService, addProduct, - updateProduct, deleteProduct, addBarber, - updateBarber, deleteBarber, updateShopDetails, } = useApp(); @@ -60,11 +55,28 @@ export default function Dashboard() { }, [appointments, shop?.id, dateFilter]); // Form states - const [editingId, setEditingId] = useState(null); const [formSvc, setFormSvc] = useState({ name: '', price: '', duration: '' }); const [formProd, setFormProd] = useState({ name: '', price: '', stock: '' }); const [formBarb, setFormBarb] = useState({ name: '', specialties: '' }); + // Settings states + const [shopName, setShopName] = useState(shop?.name || ''); + const [shopAddr, setShopAddr] = useState(shop?.address || ''); + const [phone1, setPhone1] = useState(shop?.contacts?.phone1 || ''); + const [whatsapp, setWhatsapp] = useState(shop?.socialNetworks?.whatsapp || ''); + const [instagram, setInstagram] = useState(shop?.socialNetworks?.instagram || ''); + const [facebook, setFacebook] = useState(shop?.socialNetworks?.facebook || ''); + const [paymentMethods, setPaymentMethods] = useState(shop?.paymentMethods?.join(', ') || ''); + const [schedule, setSchedule] = useState(shop?.schedule || [ + { day: 'Segunda', open: '09:00', close: '19:00' }, + { day: 'Terça', open: '09:00', close: '19:00' }, + { day: 'Quarta', open: '09:00', close: '19:00' }, + { day: 'Quinta', open: '09:00', close: '19:00' }, + { day: 'Sexta', open: '09:00', close: '19:00' }, + { day: 'Sábado', open: '09:00', close: '18:00' }, + { day: 'Domingo', open: '00:00', close: '00:00', closed: true }, + ]); + const pickImage = async (target: 'shop' | 'barber', barberId?: string) => { const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, @@ -102,7 +114,10 @@ export default function Dashboard() { await updateShopDetails(shop!.id, { imageUrl: publicUrl }); } else if (barberId) { const b = shop?.barbers.find(x => x.id === barberId); - if (b) await updateBarber(shop!.id, { ...b, imageUrl: publicUrl }); + if (b) { + // Update barber logic in context or direct supabase + await supabase.from('barbers').update({ image_url: publicUrl }).eq('id', barberId); + } } await refreshShops(); } catch (e: any) { @@ -113,6 +128,27 @@ export default function Dashboard() { } }; + const handleSaveSettings = async () => { + if (!shop) return; + setLoading(true); + try { + await updateShopDetails(shop.id, { + name: shopName, + address: shopAddr, + contacts: { phone1 }, + socialNetworks: { whatsapp, instagram, facebook }, + paymentMethods: paymentMethods.split(',').map(s => s.trim()).filter(Boolean), + schedule, + }); + Alert.alert('Sucesso', 'Definições guardadas com sucesso.'); + await refreshShops(); + } catch (e: any) { + Alert.alert('Erro', e.message); + } finally { + setLoading(false); + } + }; + if (!shop) return null; return ( @@ -120,10 +156,10 @@ export default function Dashboard() { {/* Header Dashboard */} - Olá, Parceiro + Gestão do Espaço {shop.name} - navigation.navigate('ProfileTab' as never)}> + navigation.navigate('BarberProfileTab' as never)}> {user?.name.charAt(0)} @@ -134,7 +170,7 @@ export default function Dashboard() { {[ ['agenda', 'Agenda'], ['servicos', 'Serviços'], - ['produtos', 'Inventário'], + ['produtos', 'Produtos'], ['equipa', 'Equipa'], ['perfil', 'Definições'], ].map(([id, label]) => ( @@ -153,7 +189,7 @@ export default function Dashboard() { {activeTab === 'agenda' && ( - Agenda do Dia + Marcações - Nenhum agendamento para este dia. + Sem marcações para hoje. )} @@ -205,7 +241,7 @@ export default function Dashboard() { {activeTab === 'servicos' && ( - Gerir Serviços + Serviços Disponíveis {shop.services.map(s => ( @@ -228,14 +264,46 @@ export default function Dashboard() { )} + {activeTab === 'produtos' && ( + + Inventário de Produtos + {shop.products.map(p => ( + + + {p.name} + Stock: {p.stock} · {currency(p.price)} + + deleteProduct(shop.id, p.id)} style={styles.deleteIcon}> + + + + ))} + + + Novo Produto + setFormProd({...formProd, name: t})} /> + + setFormProd({...formProd, price: t})} /> + setFormProd({...formProd, stock: t})} /> + + + + + )} + {activeTab === 'equipa' && ( - Equipa + Membros da Equipa {shop.barbers.map(b => ( pickImage('barber', b.id)} style={styles.barberAvatar}> @@ -250,12 +318,27 @@ export default function Dashboard() { ))} + + + Novo Profissional + setFormBarb({...formBarb, name: t})} /> + setFormBarb({...formBarb, specialties: t})} /> + + )} {activeTab === 'perfil' && ( - Definições do Espaço + Configurações do Espaço pickImage('shop')} style={styles.coverUpload}> {shop.imageUrl ? ( @@ -266,13 +349,62 @@ export default function Dashboard() { - Nome do Espaço - updateShopDetails(shop.id, { name: t })} /> + Informação Geral + + + - Endereço - updateShopDetails(shop.id, { address: t })} /> + Redes Sociais + + + + + Pagamentos + - + Horário de Funcionamento + {schedule.map((s, idx) => ( + + {s.day} + { + const next = [...schedule]; + next[idx] = { ...next[idx], closed: !next[idx].closed }; + setSchedule(next); + }} + style={[styles.closedBtn, s.closed && styles.closedBtnActive]} + > + + {s.closed ? 'Fechado' : 'Aberto'} + + + {!s.closed && ( + + { + const next = [...schedule]; + next[idx] = { ...next[idx], open: t }; + setSchedule(next); + }} + /> + - + { + const next = [...schedule]; + next[idx] = { ...next[idx], close: t }; + setSchedule(next); + }} + /> + + )} + + ))} + + )} @@ -517,6 +649,7 @@ const styles = StyleSheet.create({ fontSize: 14, borderWidth: 1, borderColor: 'rgba(255,255,255,0.04)', + marginBottom: 8, }, inputLabel: { color: '#94a3b8', @@ -524,6 +657,7 @@ const styles = StyleSheet.create({ fontWeight: '700', textTransform: 'uppercase', letterSpacing: 0.5, + marginBottom: 8, }, row: { flexDirection: 'row', @@ -578,4 +712,53 @@ const styles = StyleSheet.create({ fontSize: 14, fontWeight: '700', }, + scheduleRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 10, + marginBottom: 10, + }, + dayLabel: { + color: '#f8fafc', + fontSize: 13, + fontWeight: '700', + width: 65, + }, + closedBtn: { + paddingHorizontal: 8, + paddingVertical: 4, + borderRadius: 6, + backgroundColor: 'rgba(16,185,129,0.1)', + borderWidth: 1, + borderColor: 'rgba(16,185,129,0.2)', + }, + closedBtnActive: { + backgroundColor: 'rgba(239,68,68,0.1)', + borderColor: 'rgba(239,68,68,0.2)', + }, + closedText: { + color: '#10b981', + fontSize: 11, + fontWeight: '800', + }, + closedTextActive: { + color: '#ef4444', + }, + timeInputs: { + flexDirection: 'row', + alignItems: 'center', + gap: 6, + }, + timeInput: { + backgroundColor: '#1c1c2e', + borderRadius: 8, + paddingHorizontal: 8, + paddingVertical: 4, + color: '#f8fafc', + fontSize: 12, + borderWidth: 1, + borderColor: 'rgba(255,255,255,0.04)', + width: 55, + textAlign: 'center', + }, }); diff --git a/src/pages/Profile.tsx b/src/pages/Profile.tsx index 5d15a9c..5ecfbde 100644 --- a/src/pages/Profile.tsx +++ b/src/pages/Profile.tsx @@ -24,7 +24,8 @@ const statusLabel: Record = { cancelado: 'Cancelado', }; -type Tab = 'favoritos' | 'agenda' | 'pedidos'; +type ClientTab = 'favoritos' | 'agenda' | 'pedidos'; +type BarberTab = 'reviews' | 'agenda_pessoal' | 'estatisticas'; export default function Profile() { const navigation = useNavigation>(); @@ -40,41 +41,49 @@ export default function Profile() { submitReview, } = useApp(); - const [activeTab, setActiveTab] = useState('agenda'); + const [activeTab, setActiveTab] = useState(user?.role === 'barbearia' ? 'agenda_pessoal' : 'agenda'); const [reviewedAppointments, setReviewedAppointments] = useState>(new Set()); const [reviewTarget, setReviewTarget] = useState<{ appointmentId: string; shopId: string; shopName: string } | null>(null); const [rating, setRating] = useState(0); const [comment, setComment] = useState(''); const [submittingReview, setSubmittingReview] = useState(false); + const [shopReviews, setShopReviews] = useState([]); + + const isBarber = user?.role === 'barbearia'; + const myShop = useMemo(() => shops.find(s => s.id === user?.shopId), [shops, user?.shopId]); useEffect(() => { let mounted = true; (async () => { if (!user) return; - const { data } = await supabase - .from('reviews') - .select('appointment_id') - .eq('customer_id', user.id); - if (!mounted || !data) return; - setReviewedAppointments(new Set(data.map((row: any) => row.appointment_id).filter(Boolean))); + if (user.role === 'cliente') { + const { data } = await supabase.from('reviews').select('appointment_id').eq('customer_id', user.id); + if (!mounted || !data) return; + setReviewedAppointments(new Set(data.map((row: any) => row.appointment_id).filter(Boolean))); + } else if (user.role === 'barbearia' && user.shopId) { + const { data } = await supabase.from('reviews').select('*').eq('shop_id', user.shopId).order('created_at', { ascending: false }); + if (!mounted || !data) return; + setShopReviews(data); + } })(); - return () => { - mounted = false; - }; + return () => { mounted = false; }; }, [user?.id]); const myAppointments = useMemo( - () => (user ? appointments.filter((a) => a.customerId === user.id) : []), - [appointments, user?.id] + () => (user ? (isBarber ? appointments.filter(a => a.shopId === user.shopId) : appointments.filter((a) => a.customerId === user.id)) : []), + [appointments, user?.id, isBarber] ); + const myOrders = useMemo( () => (user ? orders.filter((o) => o.customerId === user.id) : []), [orders, user?.id] ); + const favoriteShops = useMemo( () => shops.filter((shop) => favorites.includes(shop.id)), [shops, favorites] ); + const myNotifications = useMemo( () => user @@ -85,19 +94,7 @@ export default function Profile() { [notifications, user?.id] ); - if (!user) { - return ( - - - Sessão expirada - Faz login para aceder ao teu perfil. - - - - ); - } + if (!user) return null; const handleReviewSubmit = async () => { if (!reviewTarget || rating === 0) return; @@ -106,8 +103,6 @@ export default function Profile() { await submitReview(reviewTarget.shopId, reviewTarget.appointmentId, rating, comment); setReviewedAppointments((prev) => new Set([...prev, reviewTarget.appointmentId])); setReviewTarget(null); - setRating(0); - setComment(''); Alert.alert('Obrigado', 'A tua avaliação foi enviada.'); } catch (e: any) { Alert.alert('Erro', e?.message || 'Erro ao enviar avaliação.'); @@ -128,7 +123,7 @@ export default function Profile() { {user.name} {user.email} - {user.role === 'cliente' ? 'Cliente' : 'Parceiro'} + {isBarber ? 'Gestor de Espaço' : 'Cliente'} @@ -155,16 +150,20 @@ export default function Profile() { )} - {/* Tabs Estilizadas */} + {/* Tabs Role-Based */} - {[ - ['agenda', 'Agenda'], + {(isBarber ? [ + ['agenda_pessoal', 'Agenda Global'], + ['reviews', 'Avaliações'], + ['estatisticas', 'Resumo'], + ] : [ + ['agenda', 'Minha Agenda'], ['favoritos', 'Favoritos'], - ['pedidos', 'Pedidos'], - ].map(([id, label]) => ( + ['pedidos', 'Compras'], + ]).map(([id, label]) => ( setActiveTab(id as Tab)} + onPress={() => setActiveTab(id)} style={[styles.tabItem, activeTab === id && styles.tabActive]} > {label} @@ -175,6 +174,7 @@ export default function Profile() { {/* Conteúdo das Tabs */} + {/* CLIENTE TABS */} {activeTab === 'agenda' && ( {myAppointments.length > 0 ? ( @@ -182,124 +182,115 @@ 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 dateStr = dateParts[0]; - const timeStr = dateParts[1] || ''; - return ( - {dateStr.split('-')[2]} - {new Date(dateStr).toLocaleString('pt-PT', { month: 'short' }).toUpperCase()} + {dateParts[0].split('-')[2]} + {new Date(dateParts[0]).toLocaleString('pt-PT', { month: 'short' }).toUpperCase()} {shop?.name || 'Barbearia'} - {timeStr} · {currency(appt.total)} + {dateParts[1]} · {currency(appt.total)} {statusLabel[appt.status]} {canReview && ( - )} ); }) - ) : ( - - Nenhum agendamento ativo. - - )} + ) : Sem marcações agendadas.} )} {activeTab === 'favoritos' && ( - {favoriteShops.length > 0 ? ( - favoriteShops.map((shop) => ( - navigation.navigate('ShopDetails', { shopId: shop.id })} - > - - - {shop.name.charAt(0)} - - - {shop.name} - {shop.address} - - ★ {shop.rating.toFixed(1)} - - - )) - ) : ( - - Sem barbearias favoritas. - - )} + {favoriteShops.map(s => ( + navigation.navigate('ShopDetails', { shopId: s.id })}> + + {s.name.charAt(0)} + {s.name}{s.address} + ★ {s.rating.toFixed(1)} + + + ))} )} - {activeTab === 'pedidos' && ( + {/* BARBER TABS */} + {activeTab === 'agenda_pessoal' && ( - {myOrders.length > 0 ? ( - myOrders.map((order) => { - const shop = shops.find((s) => s.id === order.shopId); - return ( - - - {shop?.name || 'Barbearia'} - {currency(order.total)} - - - {new Date(order.createdAt).toLocaleDateString()} - - {statusLabel[order.status]} - - - - ); - }) - ) : ( - - Ainda não tens pedidos. - - )} + 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]} + + + + ))} )} + + {activeTab === 'reviews' && ( + + {shopReviews.map(r => ( + + + {'★'.repeat(r.rating)} + {new Date(r.created_at).toLocaleDateString()} + + {r.comment || 'Sem comentário.'} + + ))} + + )} + + {activeTab === 'estatisticas' && ( + + + Total de Marcações + {myAppointments.length} + + + Rating Médio + {myShop?.rating.toFixed(1)} ★ + + + Vendas em Produtos + {currency(myOrders.reduce((sum, o) => sum + o.total, 0))} + + + )} - {/* Modal de Avaliação (Inline) */} + {/* Review Modal for Clients */} {reviewTarget && ( - Como foi o serviço na {reviewTarget.shopName}? + Avaliar {reviewTarget.shopName} - {[1, 2, 3, 4, 5].map((s) => ( + {[1,2,3,4,5].map(s => ( setRating(s)}> = s && styles.starActive]}>★ ))} - + - + )} @@ -309,319 +300,65 @@ export default function Profile() { } const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#0a0a0f', - }, - content: { - padding: 20, - paddingBottom: 100, - }, - profileHeader: { - flexDirection: 'row', - alignItems: 'center', - marginBottom: 32, - gap: 16, - }, - avatarBox: { - width: 64, - height: 64, - borderRadius: 22, - backgroundColor: '#141420', - alignItems: 'center', - justifyContent: 'center', - borderWidth: 1, - borderColor: 'rgba(99,102,241,0.3)', - }, - avatarText: { - color: '#818cf8', - fontSize: 24, - fontWeight: '900', - }, - profileInfo: { - flex: 1, - gap: 2, - }, - profileName: { - color: '#f8fafc', - fontSize: 22, - fontWeight: '900', - }, - profileEmail: { - color: '#64748b', - fontSize: 14, - fontWeight: '500', - }, - roleBadge: { - alignSelf: 'flex-start', - backgroundColor: 'rgba(99,102,241,0.1)', - borderRadius: 8, - paddingHorizontal: 8, - paddingVertical: 3, - marginTop: 4, - }, - roleText: { - color: '#818cf8', - fontSize: 10, - fontWeight: '800', - textTransform: 'uppercase', - }, - logoutBtn: { - padding: 8, - }, - logoutIcon: { - color: '#ef4444', - fontSize: 24, - fontWeight: '700', - }, - sectionTitle: { - color: '#f8fafc', - fontSize: 18, - fontWeight: '800', - marginBottom: 12, - }, - notifSection: { - marginBottom: 32, - }, - notifScroll: { - marginHorizontal: -20, - paddingHorizontal: 20, - }, - notifCard: { - backgroundColor: '#141420', - width: 240, - borderRadius: 16, - padding: 14, - marginRight: 12, - borderWidth: 1, - borderColor: 'rgba(255,255,255,0.06)', - gap: 8, - }, - notifUnread: { - borderColor: 'rgba(99,102,241,0.4)', - }, - notifMsg: { - color: '#94a3b8', - fontSize: 13, - lineHeight: 18, - }, - markRead: { - color: '#818cf8', - fontSize: 12, - fontWeight: '700', - }, - tabBar: { - flexDirection: 'row', - marginBottom: 24, - borderBottomWidth: 1, - borderBottomColor: 'rgba(255,255,255,0.06)', - }, - tabItem: { - paddingVertical: 12, - marginRight: 24, - position: 'relative', - }, + container: { flex: 1, backgroundColor: '#0a0a0f' }, + content: { padding: 20, paddingBottom: 100 }, + profileHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 32, gap: 16 }, + avatarBox: { width: 64, height: 64, borderRadius: 22, backgroundColor: '#141420', alignItems: 'center', justifyContent: 'center', borderWidth: 1, borderColor: 'rgba(99,102,241,0.3)' }, + avatarText: { color: '#818cf8', fontSize: 24, fontWeight: '900' }, + profileInfo: { flex: 1, gap: 2 }, + profileName: { color: '#f8fafc', fontSize: 22, fontWeight: '900' }, + profileEmail: { color: '#64748b', fontSize: 14, fontWeight: '500' }, + roleBadge: { alignSelf: 'flex-start', backgroundColor: 'rgba(99,102,241,0.1)', borderRadius: 8, paddingHorizontal: 8, paddingVertical: 3, marginTop: 4 }, + roleText: { color: '#818cf8', fontSize: 10, fontWeight: '800', textTransform: 'uppercase' }, + logoutBtn: { padding: 8 }, + logoutIcon: { color: '#ef4444', fontSize: 24, fontWeight: '700' }, + sectionTitle: { color: '#f8fafc', fontSize: 18, fontWeight: '800', marginBottom: 12 }, + notifSection: { marginBottom: 32 }, + notifScroll: { marginHorizontal: -20, paddingHorizontal: 20 }, + notifCard: { backgroundColor: '#141420', width: 240, borderRadius: 16, padding: 14, marginRight: 12, borderWidth: 1, borderColor: 'rgba(255,255,255,0.06)', gap: 8 }, + notifUnread: { borderColor: 'rgba(99,102,241,0.4)' }, + notifMsg: { color: '#94a3b8', fontSize: 13, lineHeight: 18 }, + markRead: { color: '#818cf8', fontSize: 12, fontWeight: '700' }, + tabBar: { flexDirection: 'row', marginBottom: 24, borderBottomWidth: 1, borderBottomColor: 'rgba(255,255,255,0.06)' }, + tabItem: { paddingVertical: 12, marginRight: 24, position: 'relative' }, tabActive: {}, - tabText: { - color: '#475569', - fontSize: 15, - fontWeight: '700', - }, - tabTextActive: { - color: '#a5b4fc', - }, - tabIndicator: { - position: 'absolute', - bottom: -1, - left: 0, - right: 0, - height: 2, - backgroundColor: '#6366f1', - borderRadius: 2, - }, - tabContent: { - minHeight: 200, - }, - listContainer: { - gap: 12, - }, - agendaCard: { - padding: 14, - gap: 12, - }, - agendaTop: { - flexDirection: 'row', - alignItems: 'center', - gap: 12, - }, - dateBox: { - backgroundColor: '#1c1c2e', - padding: 8, - borderRadius: 12, - alignItems: 'center', - minWidth: 50, - }, - dateDay: { - color: '#f8fafc', - fontSize: 18, - fontWeight: '900', - }, - dateMonth: { - color: '#6366f1', - fontSize: 10, - fontWeight: '800', - }, - agendaMain: { - flex: 1, - gap: 2, - }, - agendaShop: { - color: '#f8fafc', - fontSize: 16, - fontWeight: '800', - }, - agendaTime: { - color: '#64748b', - fontSize: 13, - fontWeight: '500', - }, - statusTag: { - paddingHorizontal: 10, - paddingVertical: 5, - borderRadius: 10, - }, - statusText: { - fontSize: 11, - fontWeight: '800', - textTransform: 'uppercase', - }, - reviewBtn: { - marginTop: 4, - }, - shopCard: { - flexDirection: 'row', - alignItems: 'center', - gap: 12, - padding: 12, - }, - shopIcon: { - width: 44, - height: 44, - borderRadius: 12, - backgroundColor: 'rgba(255,255,255,0.03)', - alignItems: 'center', - justifyContent: 'center', - }, - shopIconText: { - color: '#475569', - fontSize: 18, - fontWeight: '800', - }, - shopName: { - color: '#f8fafc', - fontSize: 16, - fontWeight: '800', - }, - shopAddr: { - color: '#475569', - fontSize: 12, - }, - shopRating: { - color: '#fbbf24', - fontWeight: '800', - fontSize: 13, - }, - orderCard: { - padding: 16, - gap: 8, - }, - orderHeader: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - }, - orderShop: { - color: '#f8fafc', - fontSize: 16, - fontWeight: '800', - }, - orderPrice: { - color: '#a5b4fc', - fontSize: 16, - fontWeight: '900', - }, - orderFooter: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - }, - orderDate: { - color: '#475569', - fontSize: 12, - }, - emptyBox: { - alignItems: 'center', - paddingVertical: 40, - }, - emptyText: { - color: '#475569', - fontSize: 14, - fontWeight: '600', - }, - reviewModal: { - marginTop: 24, - padding: 20, - gap: 16, - borderColor: 'rgba(99,102,241,0.3)', - }, - reviewTitle: { - color: '#f8fafc', - fontSize: 16, - fontWeight: '800', - textAlign: 'center', - }, - stars: { - flexDirection: 'row', - justifyContent: 'center', - gap: 8, - }, - star: { - fontSize: 32, - color: '#1c1c2e', - }, - starActive: { - color: '#fbbf24', - }, - reviewInput: { - backgroundColor: '#1c1c2e', - borderRadius: 14, - padding: 12, - color: '#f8fafc', - height: 80, - textAlignVertical: 'top', - }, - reviewActions: { - flexDirection: 'row', - gap: 12, - }, - centerState: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - padding: 40, - gap: 16, - }, - centerTitle: { - color: '#f8fafc', - fontSize: 22, - fontWeight: '900', - }, - centerText: { - color: '#64748b', - textAlign: 'center', - lineHeight: 20, - }, - darkButton: { - width: '100%', - }, + tabText: { color: '#475569', fontSize: 15, fontWeight: '700' }, + tabTextActive: { color: '#a5b4fc' }, + tabIndicator: { position: 'absolute', bottom: -1, left: 0, right: 0, height: 2, backgroundColor: '#6366f1', borderRadius: 2 }, + tabContent: { minHeight: 200 }, + listContainer: { gap: 12 }, + emptyTxt: { color: '#475569', textAlign: 'center', marginTop: 20 }, + subTitle: { color: '#64748b', fontSize: 13, fontWeight: '700', textTransform: 'uppercase', marginBottom: 8 }, + agendaCard: { padding: 14, gap: 12 }, + agendaTop: { flexDirection: 'row', alignItems: 'center', gap: 12 }, + dateBox: { backgroundColor: '#1c1c2e', padding: 8, borderRadius: 12, alignItems: 'center', minWidth: 50 }, + dateDay: { color: '#f8fafc', fontSize: 18, fontWeight: '900' }, + dateMonth: { color: '#6366f1', fontSize: 10, fontWeight: '800' }, + agendaMain: { flex: 1, gap: 2 }, + agendaShop: { color: '#f8fafc', fontSize: 16, fontWeight: '800' }, + agendaTime: { color: '#64748b', fontSize: 13 }, + statusTag: { paddingHorizontal: 10, paddingVertical: 5, borderRadius: 10 }, + statusText: { fontSize: 11, fontWeight: '800', textTransform: 'uppercase' }, + reviewBtn: { marginTop: 4 }, + shopCard: { flexDirection: 'row', alignItems: 'center', gap: 12, padding: 12 }, + shopIcon: { width: 44, height: 44, borderRadius: 12, backgroundColor: 'rgba(255,255,255,0.03)', alignItems: 'center', justifyContent: 'center' }, + shopIconText: { color: '#475569', fontSize: 18, fontWeight: '800' }, + shopName: { color: '#f8fafc', fontSize: 16, fontWeight: '800' }, + shopAddr: { color: '#475569', fontSize: 12 }, + shopRating: { color: '#fbbf24', fontWeight: '800', fontSize: 13 }, + reviewCard: { padding: 16, gap: 8 }, + reviewHeader: { flexDirection: 'row', justifyContent: 'space-between' }, + reviewStars: { color: '#fbbf24', fontSize: 14 }, + reviewDate: { color: '#475569', fontSize: 11 }, + reviewComment: { color: '#f8fafc', fontSize: 13, lineHeight: 18 }, + statCard: { padding: 20, alignItems: 'center', gap: 4 }, + statLabel: { color: '#64748b', fontSize: 12, fontWeight: '700', textTransform: 'uppercase' }, + statValue: { color: '#f8fafc', fontSize: 24, fontWeight: '900' }, + reviewModal: { marginTop: 24, padding: 20, gap: 16, borderColor: 'rgba(99,102,241,0.3)' }, + reviewTitle: { color: '#f8fafc', fontSize: 16, fontWeight: '800', textAlign: 'center' }, + stars: { flexDirection: 'row', justifyContent: 'center', gap: 8 }, + star: { fontSize: 32, color: '#1c1c2e' }, + starActive: { color: '#fbbf24' }, + reviewInput: { backgroundColor: '#1c1c2e', borderRadius: 14, padding: 12, color: '#f8fafc', height: 80, textAlignVertical: 'top' }, + reviewActions: { flexDirection: 'row', gap: 12 }, });