From 3988535f16306265f2fd2d520397008343871861 Mon Sep 17 00:00:00 2001 From: 230417 <230417@epvc.pt> Date: Thu, 14 May 2026 09:58:39 +0100 Subject: [PATCH] refactor: add logout section with version display to profile and dashboard, and implement conflict check for appointment confirmation --- src/context/AppContext.tsx | 25 ++++++++- src/pages/Dashboard.tsx | 43 ++++++++++++++++ src/pages/Profile.tsx | 101 ++++++++++++++----------------------- 3 files changed, 105 insertions(+), 64 deletions(-) diff --git a/src/context/AppContext.tsx b/src/context/AppContext.tsx index 1b1e9b3..66e5f48 100644 --- a/src/context/AppContext.tsx +++ b/src/context/AppContext.tsx @@ -522,9 +522,30 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { const updateAppointmentStatus = async (id: string, status: Appointment['status']) => { const apt = appointments.find((a) => a.id === id); - await supabase.from('appointments').update({ status }).eq('id', id); + if (!apt) return; - if (status === 'cancelado' && apt) { + if (status === 'confirmado') { + // Check if there is already a confirmed appointment for this barber at this time + const conflict = appointments.find(a => + a.barberId === apt.barberId && + a.date === apt.date && + a.status === 'confirmado' && + a.id !== id + ); + + if (conflict) { + Alert.alert('Conflito de Horário', 'Já existe uma marcação confirmada para este horário com este profissional.'); + return; + } + } + + const { error } = await supabase.from('appointments').update({ status }).eq('id', id); + if (error) { + Alert.alert('Erro', 'Não foi possível atualizar o estado da marcação.'); + return; + } + + if (status === 'cancelado') { const waitlistDate = apt.date.split(' ')[0]; // Extract YYYY-MM-DD const waitingUsers = waitlists.filter(w => w.barberId === apt.barberId && w.date === waitlistDate && w.status === 'pending'); diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 83ad53b..77cd6c8 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -440,6 +440,14 @@ export default function Dashboard() { ))} + + + + Sair da Conta + + + Smart Agenda v1.0.4 + )} @@ -813,4 +821,39 @@ const styles = StyleSheet.create({ width: 55, textAlign: 'center', }, + logoutSection: { + marginTop: 40, + paddingTop: 20, + borderTopWidth: 1, + borderTopColor: 'rgba(255,255,255,0.06)', + alignItems: 'center', + }, + logoutButtonPremium: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'rgba(239,68,68,0.1)', + paddingVertical: 16, + paddingHorizontal: 24, + borderRadius: 16, + width: '100%', + borderWidth: 1, + borderColor: 'rgba(239,68,68,0.2)', + gap: 12, + }, + logoutButtonText: { + color: '#ef4444', + fontSize: 16, + fontWeight: '800', + }, + logoutButtonIcon: { + color: '#ef4444', + fontSize: 20, + }, + versionText: { + color: '#334155', + fontSize: 12, + marginTop: 16, + fontWeight: '600', + }, }); diff --git a/src/pages/Profile.tsx b/src/pages/Profile.tsx index 459351d..4e9c01b 100644 --- a/src/pages/Profile.tsx +++ b/src/pages/Profile.tsx @@ -126,11 +126,7 @@ export default function Profile() { {isBarber ? 'Gestor de Espaço' : 'Cliente'} - - - - - + {/* Notificações (Horizontal) */} @@ -296,6 +292,14 @@ export default function Profile() { )} + + + + Sair da Conta + + + Smart Agenda v1.0.4 + ); @@ -312,67 +316,40 @@ const styles = StyleSheet.create({ 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: 4, + reviewActions: { flexDirection: 'row', gap: 14 }, + logoutSection: { + marginTop: 60, + paddingTop: 30, + borderTopWidth: 1, + borderTopColor: 'rgba(255,255,255,0.06)', + alignItems: 'center', }, - logoutIconBg: { - width: 40, - height: 40, - borderRadius: 12, - backgroundColor: 'rgba(239,68,68,0.1)', + logoutButtonPremium: { + flexDirection: 'row', alignItems: 'center', justifyContent: 'center', + backgroundColor: 'rgba(239,68,68,0.1)', + paddingVertical: 18, + paddingHorizontal: 24, + borderRadius: 20, + width: '100%', borderWidth: 1, borderColor: 'rgba(239,68,68,0.2)', + gap: 12, + }, + logoutButtonText: { + color: '#ef4444', + fontSize: 16, + fontWeight: '800', + }, + logoutButtonIcon: { + color: '#ef4444', + fontSize: 20, + }, + versionText: { + color: '#334155', + fontSize: 12, + marginTop: 16, + fontWeight: '600', }, - logoutIcon: { color: '#ef4444', fontSize: 20, 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: 20, padding: 16, marginRight: 12, borderWidth: 1, borderColor: 'rgba(255,255,255,0.06)', gap: 8, elevation: 2 }, - notifUnread: { borderColor: 'rgba(99,102,241,0.3)', backgroundColor: '#18182b' }, - notifMsg: { color: '#94a3b8', fontSize: 13, lineHeight: 18, fontWeight: '500' }, - markRead: { color: '#818cf8', fontSize: 12, fontWeight: '800', textTransform: 'uppercase', letterSpacing: 0.5 }, - tabBar: { flexDirection: 'row', marginBottom: 24, borderBottomWidth: 1, borderBottomColor: 'rgba(255,255,255,0.06)' }, - tabItem: { paddingVertical: 14, marginRight: 24, position: 'relative' }, - tabActive: {}, - tabText: { color: '#475569', fontSize: 15, fontWeight: '700' }, - tabTextActive: { color: '#f8fafc' }, - tabIndicator: { position: 'absolute', bottom: -1, left: 0, right: 0, height: 3, backgroundColor: '#6366f1', borderRadius: 4 }, - tabContent: { minHeight: 200 }, - listContainer: { gap: 16 }, - emptyTxt: { color: '#475569', textAlign: 'center', marginTop: 20, fontWeight: '600' }, - subTitle: { color: '#64748b', fontSize: 13, fontWeight: '700', textTransform: 'uppercase', marginBottom: 8, letterSpacing: 1 }, - agendaCard: { padding: 16, gap: 12, backgroundColor: '#141420', borderRadius: 20, borderWidth: 1, borderColor: 'rgba(255,255,255,0.03)' }, - agendaTop: { flexDirection: 'row', alignItems: 'center', gap: 14 }, - dateBox: { backgroundColor: '#1c1c2e', padding: 10, borderRadius: 14, alignItems: 'center', minWidth: 55, borderWidth: 1, borderColor: 'rgba(255,255,255,0.05)' }, - dateDay: { color: '#f8fafc', fontSize: 20, fontWeight: '900' }, - dateMonth: { color: '#818cf8', fontSize: 10, fontWeight: '800', textTransform: 'uppercase' }, - agendaMain: { flex: 1, gap: 3 }, - agendaShop: { color: '#f8fafc', fontSize: 17, fontWeight: '800' }, - agendaTime: { color: '#64748b', fontSize: 13, fontWeight: '500' }, - statusTag: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 10 }, - statusText: { fontSize: 10, fontWeight: '900', textTransform: 'uppercase', letterSpacing: 0.5 }, - reviewBtn: { marginTop: 8, borderRadius: 12 }, - shopCard: { flexDirection: 'row', alignItems: 'center', gap: 14, padding: 16, backgroundColor: '#141420', borderRadius: 20, marginBottom: 4 }, - shopIcon: { width: 48, height: 48, borderRadius: 14, backgroundColor: 'rgba(99,102,241,0.05)', alignItems: 'center', justifyContent: 'center', borderWidth: 1, borderColor: 'rgba(99,102,241,0.1)' }, - shopIconText: { color: '#818cf8', fontSize: 20, fontWeight: '900' }, - shopName: { color: '#f8fafc', fontSize: 16, fontWeight: '800' }, - shopAddr: { color: '#475569', fontSize: 12, fontWeight: '500' }, - shopRating: { color: '#fbbf24', fontWeight: '900', fontSize: 14 }, - reviewCard: { padding: 18, gap: 10, backgroundColor: '#141420', borderRadius: 20 }, - reviewHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, - reviewStars: { color: '#fbbf24', fontSize: 15 }, - reviewDate: { color: '#475569', fontSize: 12, fontWeight: '600' }, - reviewComment: { color: '#f8fafc', fontSize: 14, lineHeight: 20, fontWeight: '400' }, - statCard: { padding: 24, alignItems: 'center', gap: 6, backgroundColor: '#141420', borderRadius: 24, borderWidth: 1, borderColor: 'rgba(99,102,241,0.1)' }, - statLabel: { color: '#64748b', fontSize: 12, fontWeight: '700', textTransform: 'uppercase', letterSpacing: 1 }, - statValue: { color: '#f8fafc', fontSize: 28, fontWeight: '900' }, - reviewModal: { marginTop: 24, padding: 24, gap: 16, borderColor: 'rgba(99,102,241,0.3)', borderRadius: 24 }, - reviewTitle: { color: '#f8fafc', fontSize: 18, fontWeight: '800', textAlign: 'center' }, - stars: { flexDirection: 'row', justifyContent: 'center', gap: 12 }, - star: { fontSize: 36, color: '#1c1c2e' }, - starActive: { color: '#fbbf24' }, - reviewInput: { backgroundColor: '#1c1c2e', borderRadius: 16, padding: 16, color: '#f8fafc', height: 100, textAlignVertical: 'top', borderWidth: 1, borderColor: 'rgba(255,255,255,0.05)' }, - reviewActions: { flexDirection: 'row', gap: 14 }, });