Update Dashboard stats layout to a premium unified card

This commit is contained in:
2026-05-26 16:54:22 +01:00
parent 6726c4d539
commit 0c591bccf2
2 changed files with 220 additions and 128 deletions

View File

@@ -195,26 +195,31 @@ export default function Dashboard() {
</View>
<ScrollView contentContainerStyle={styles.content}>
<View style={styles.overviewGrid}>
<Card style={styles.overviewCard}>
<Text style={styles.overviewValue}>{shopAppointments.length}</Text>
<Text style={styles.overviewLabel}>Hoje</Text>
</Card>
<Card style={styles.overviewCard}>
<Text style={styles.overviewValue}>{shop.services.length}</Text>
<Text style={styles.overviewLabel}>Serviços</Text>
</Card>
<Card style={styles.overviewCardWide}>
<View>
<Text style={styles.overviewValue}>{shop.products.reduce((sum, p) => sum + p.stock, 0)}</Text>
<Text style={styles.overviewLabel}>Produtos em stock</Text>
</View>
<Text style={styles.overviewRating}> {shop.rating.toFixed(1)}</Text>
</Card>
</View>
{activeTab === 'agenda' && (
<View style={styles.tabBox}>
<View style={styles.proStatsCard}>
<View style={styles.proMainArea}>
<Text style={styles.proMainLabel}>Marcações de Hoje</Text>
<Text style={styles.proMainValue}>{shopAppointments.length}</Text>
</View>
<View style={styles.proSubArea}>
<View style={styles.proSubItem}>
<Text style={styles.proSubValue}>{shop.services.length}</Text>
<Text style={styles.proSubLabel}>Serviços</Text>
</View>
<View style={styles.proSubDivider} />
<View style={styles.proSubItem}>
<Text style={styles.proSubValue}>{shop.products.reduce((sum, p) => sum + p.stock, 0)}</Text>
<Text style={styles.proSubLabel}>Stock</Text>
</View>
<View style={styles.proSubDivider} />
<View style={styles.proSubItem}>
<Text style={[styles.proSubValue, { color: colors.star }]}> {shop.rating.toFixed(1)}</Text>
<Text style={styles.proSubLabel}>Avaliação</Text>
</View>
</View>
</View>
<View style={styles.agendaHeader}>
<Text style={styles.sectionTitle}>Marcações</Text>
<TextInput
@@ -555,39 +560,60 @@ const styles = StyleSheet.create({
paddingBottom: 40,
gap: 20,
},
overviewGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 10,
proStatsCard: {
backgroundColor: colors.surface,
borderRadius: radius.xl,
overflow: 'hidden',
marginBottom: 8,
...shadows.card,
},
overviewCard: {
flex: 1,
minWidth: '47%',
padding: 16,
gap: 4,
},
overviewCardWide: {
width: '100%',
padding: 16,
flexDirection: 'row',
proMainArea: {
backgroundColor: colors.surfaceDark,
padding: 24,
alignItems: 'center',
justifyContent: 'space-between',
justifyContent: 'center',
},
overviewValue: {
color: colors.text,
fontSize: 26,
fontWeight: '900',
},
overviewLabel: {
color: colors.textMuted,
proMainLabel: {
color: '#87948f', // A specific tone that looks good on dark
fontSize: 12,
fontWeight: '800',
textTransform: 'uppercase',
letterSpacing: 1.5,
marginBottom: 8,
},
overviewRating: {
color: colors.star,
proMainValue: {
color: colors.textInverse,
fontSize: 48,
fontWeight: '900',
},
proSubArea: {
flexDirection: 'row',
paddingVertical: 18,
backgroundColor: colors.surface,
},
proSubItem: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
proSubDivider: {
width: 1,
backgroundColor: colors.border,
height: '60%',
alignSelf: 'center',
},
proSubValue: {
color: colors.text,
fontSize: 18,
fontWeight: '900',
marginBottom: 4,
},
proSubLabel: {
color: colors.textMuted,
fontSize: 11,
fontWeight: '700',
textTransform: 'uppercase',
letterSpacing: 0.5,
},
tabBox: {
gap: 20,

View File

@@ -101,6 +101,7 @@ export default function Profile() {
);
const activeAppointmentsCount = myAppointments.filter((a) => a.status !== 'cancelado').length;
const totalOrdersValue = myOrders.reduce((sum, o) => sum + o.total, 0);
const unreadCount = myNotifications.filter((n) => !n.read).length;
if (!user) return null;
@@ -123,35 +124,42 @@ export default function Profile() {
<SafeAreaView style={styles.container} edges={['top']}>
<ScrollView contentContainerStyle={styles.content} showsVerticalScrollIndicator={false}>
{/* Profile Card Header - Premium Styling */}
{/* Profile Card Header with Accent Bar */}
<View style={styles.profileHeader}>
<View style={styles.avatarGradient}>
<Text style={styles.avatarText}>{user.name.charAt(0).toUpperCase()}</Text>
</View>
<View style={styles.profileInfo}>
<Text style={styles.profileName} numberOfLines={1}>{user.name}</Text>
<Text style={styles.profileEmail} numberOfLines={1}>{user.email}</Text>
<View style={styles.roleBadge}>
<Ionicons
name={isBarber ? "shield-checkmark-outline" : "person-outline"}
size={11}
color={colors.primaryDark}
/>
<Text style={styles.roleText}>{isBarber ? 'Gestor de Espaço' : 'Cliente'}</Text>
<View style={styles.accentBar} />
<View style={styles.profileHeaderInner}>
<View style={styles.avatarRing}>
<View style={styles.avatarGradient}>
<Text style={styles.avatarText}>{user.name.charAt(0).toUpperCase()}</Text>
</View>
</View>
<View style={styles.profileInfo}>
<Text style={styles.profileName} numberOfLines={1}>{user.name}</Text>
<Text style={styles.profileEmail} numberOfLines={1}>{user.email}</Text>
<View style={styles.roleBadge}>
<Ionicons
name={isBarber ? "shield-checkmark-outline" : "person-outline"}
size={11}
color={colors.primaryDark}
/>
<Text style={styles.roleText}>{isBarber ? 'Gestor de Espaço' : 'Cliente'}</Text>
</View>
</View>
</View>
</View>
{/* Stats Strip */}
{/* Stats Strip - Unified Card with Dividers */}
<View style={styles.statsStrip}>
<View style={styles.profileStat}>
<Text style={styles.profileStatValue}>{activeAppointmentsCount}</Text>
<Text style={styles.profileStatLabel}>{isBarber ? 'Marcações' : 'Agenda'}</Text>
</View>
<View style={styles.statsDivider} />
<View style={styles.profileStat}>
<Text style={styles.profileStatValue}>{isBarber ? shopReviews.length : favoriteShops.length}</Text>
<Text style={styles.profileStatLabel}>{isBarber ? 'Reviews' : 'Favoritos'}</Text>
</View>
<View style={styles.statsDivider} />
<View style={styles.profileStat}>
<Text style={styles.profileStatValue}>
{isBarber ? (myShop?.rating || 0).toFixed(1) : currency(totalOrdersValue)}
@@ -166,6 +174,11 @@ export default function Profile() {
<View style={styles.sectionHeaderRow}>
<Ionicons name="notifications-outline" size={18} color={colors.primary} />
<Text style={styles.sectionTitle}>Notificações Recentes</Text>
{unreadCount > 0 && (
<View style={styles.notifCountBadge}>
<Text style={styles.notifCountText}>{unreadCount}</Text>
</View>
)}
</View>
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.notifScroll} contentContainerStyle={{ paddingRight: 20 }}>
{myNotifications.map((n) => (
@@ -432,29 +445,31 @@ export default function Profile() {
)}
{activeTab === 'estatisticas' && (
<View style={styles.listContainer}>
<Card style={styles.statCard}>
<View style={styles.statIconContainer}>
<Ionicons name="calendar" size={24} color={colors.primary} />
</View>
<Text style={styles.statLabel}>Total de Marcações</Text>
<Text style={styles.statValue}>{myAppointments.length}</Text>
<View style={styles.listContainer}>
<View style={styles.statsGrid}>
<Card style={styles.statCardHalf}>
<View style={[styles.statIconContainer, { backgroundColor: colors.primarySoft }]}>
<Ionicons name="calendar" size={22} color={colors.primary} />
</View>
<Text style={styles.statValue}>{myAppointments.length}</Text>
<Text style={styles.statLabel}>Total de Marcações</Text>
</Card>
<Card style={styles.statCard}>
<View style={styles.statIconContainer}>
<Ionicons name="star" size={24} color={colors.accent} />
</View>
<Text style={styles.statLabel}>Rating Médio</Text>
<Text style={styles.statValue}>{(myShop?.rating || 0).toFixed(1)} </Text>
<Card style={styles.statCardHalf}>
<View style={[styles.statIconContainer, { backgroundColor: colors.accentSoft }]}>
<Ionicons name="star" size={22} color={colors.accent} />
</View>
<Text style={styles.statValue}>{(myShop?.rating || 0).toFixed(1)} </Text>
<Text style={styles.statLabel}>Rating Médio</Text>
</Card>
<Card style={styles.statCard}>
<View style={styles.statIconContainer}>
<Ionicons name="wallet" size={24} color={colors.success} />
</View>
<Text style={styles.statLabel}>Vendas em Produtos</Text>
<Text style={styles.statValue}>{currency(myOrders.reduce((sum, o) => sum + o.total, 0))}</Text>
</Card>
</View>
</View>
<Card style={styles.statCardFull}>
<View style={[styles.statIconContainer, { backgroundColor: colors.successSoft }]}>
<Ionicons name="wallet" size={22} color={colors.success} />
</View>
<Text style={styles.statValue}>{currency(myOrders.reduce((sum, o) => sum + o.total, 0))}</Text>
<Text style={styles.statLabel}>Vendas em Produtos</Text>
</Card>
</View>
)}
</View>
@@ -489,7 +504,7 @@ export default function Profile() {
</Card>
)}
{/* Logout Section with Premium Styling */}
{/* Logout Section */}
<View style={styles.logoutSection}>
<TouchableOpacity style={styles.logoutButtonPremium} onPress={logout} activeOpacity={0.85}>
<Text style={styles.logoutButtonText}>Sair da Conta</Text>
@@ -512,13 +527,12 @@ const styles = StyleSheet.create({
paddingBottom: 40,
gap: 20,
},
/* ── Profile Header ── */
profileHeader: {
flexDirection: 'row',
alignItems: 'center',
gap: 16,
backgroundColor: colors.surfaceDark,
borderRadius: radius.lg,
padding: 20,
overflow: 'hidden',
shadowColor: colors.primaryDark,
shadowOffset: { width: 0, height: 6 },
shadowOpacity: 0.1,
@@ -528,6 +542,27 @@ const styles = StyleSheet.create({
borderColor: 'rgba(255,255,255,0.04)',
marginTop: 6,
},
accentBar: {
height: 4,
backgroundColor: colors.primary,
width: '100%',
},
profileHeaderInner: {
flexDirection: 'row',
alignItems: 'center',
gap: 16,
padding: 20,
},
avatarRing: {
width: 72,
height: 72,
borderRadius: 36,
borderWidth: 2.5,
borderColor: 'rgba(15,118,110,0.35)',
alignItems: 'center',
justifyContent: 'center',
padding: 2,
},
avatarGradient: {
width: 62,
height: 62,
@@ -535,16 +570,14 @@ const styles = StyleSheet.create({
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,
shadowOpacity: 0.15,
shadowRadius: 4,
},
avatarText: {
color: colors.textInverse,
fontSize: 22,
fontSize: 24,
fontWeight: '900',
},
profileInfo: {
@@ -579,23 +612,27 @@ const styles = StyleSheet.create({
fontWeight: '800',
textTransform: 'uppercase',
},
/* ── Stats Strip (unified card) ── */
statsStrip: {
flexDirection: 'row',
gap: 10,
alignItems: 'center',
backgroundColor: colors.surface,
borderRadius: radius.lg,
borderWidth: 1,
borderColor: 'rgba(15,118,110,0.06)',
...shadows.soft,
},
profileStat: {
flex: 1,
backgroundColor: colors.surface,
borderRadius: radius.lg,
padding: 14,
borderWidth: 1,
borderColor: 'rgba(15,118,110,0.06)',
paddingVertical: 14,
paddingHorizontal: 8,
alignItems: 'center',
shadowColor: '#102018',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.03,
shadowRadius: 8,
elevation: 2,
},
statsDivider: {
width: 1,
height: 32,
backgroundColor: colors.border,
},
profileStatValue: {
color: colors.text,
@@ -609,6 +646,8 @@ const styles = StyleSheet.create({
textTransform: 'uppercase',
marginTop: 2,
},
/* ── Notifications ── */
sectionHeaderRow: {
flexDirection: 'row',
alignItems: 'center',
@@ -620,6 +659,21 @@ const styles = StyleSheet.create({
fontSize: 16,
fontWeight: '800',
},
notifCountBadge: {
backgroundColor: colors.danger,
borderRadius: radius.pill,
minWidth: 20,
height: 20,
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 6,
marginLeft: 4,
},
notifCountText: {
color: colors.textInverse,
fontSize: 11,
fontWeight: '800',
},
notifSection: {
marginTop: 4,
},
@@ -630,17 +684,13 @@ const styles = StyleSheet.create({
notifCard: {
backgroundColor: colors.surface,
width: 240,
borderRadius: radius.md,
borderRadius: radius.lg,
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,
...shadows.soft,
},
notifUnread: {
borderColor: 'rgba(15,118,110,0.2)',
@@ -674,6 +724,8 @@ const styles = StyleSheet.create({
textTransform: 'uppercase',
letterSpacing: 0.3,
},
/* ── Tabs ── */
tabBarContainer: {
marginVertical: 4,
},
@@ -704,6 +756,8 @@ const styles = StyleSheet.create({
tabContent: {
minHeight: 200,
},
/* ── Lists & Empty ── */
listContainer: {
gap: 12,
},
@@ -730,17 +784,15 @@ const styles = StyleSheet.create({
fontSize: 14,
fontWeight: '800',
},
/* ── Agenda Cards ── */
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,
...shadows.soft,
},
agendaTop: {
flexDirection: 'row',
@@ -811,6 +863,8 @@ const styles = StyleSheet.create({
marginTop: 10,
borderRadius: radius.md,
},
/* ── Shop / Favorite Cards ── */
shopCard: {
flexDirection: 'row',
alignItems: 'center',
@@ -820,11 +874,7 @@ const styles = StyleSheet.create({
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,
...shadows.soft,
},
shopIcon: {
width: 44,
@@ -875,6 +925,8 @@ const styles = StyleSheet.create({
fontWeight: '800',
fontSize: 11,
},
/* ── Order Cards ── */
orderCard: {
padding: 14,
gap: 10,
@@ -882,11 +934,7 @@ const styles = StyleSheet.create({
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,
...shadows.soft,
},
orderTop: {
flexDirection: 'row',
@@ -914,6 +962,8 @@ const styles = StyleSheet.create({
fontSize: 16,
fontWeight: '900',
},
/* ── Review Cards ── */
reviewCard: {
padding: 16,
gap: 8,
@@ -921,6 +971,7 @@ const styles = StyleSheet.create({
borderRadius: radius.lg,
borderWidth: 1,
borderColor: 'rgba(15,118,110,0.06)',
...shadows.soft,
},
reviewHeader: {
flexDirection: 'row',
@@ -942,7 +993,14 @@ const styles = StyleSheet.create({
lineHeight: 18,
fontWeight: '400',
},
statCard: {
/* ── Stats Grid (Barber Estatísticas Tab) ── */
statsGrid: {
flexDirection: 'row',
gap: 12,
},
statCardHalf: {
flex: 1,
padding: 20,
alignItems: 'center',
gap: 6,
@@ -950,11 +1008,17 @@ const styles = StyleSheet.create({
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,
...shadows.soft,
},
statCardFull: {
padding: 20,
alignItems: 'center',
gap: 6,
backgroundColor: colors.surface,
borderRadius: radius.lg,
borderWidth: 1,
borderColor: 'rgba(15,118,110,0.06)',
...shadows.soft,
},
statIconContainer: {
width: 40,
@@ -964,8 +1028,6 @@ const styles = StyleSheet.create({
alignItems: 'center',
justifyContent: 'center',
marginBottom: 4,
borderWidth: 1,
borderColor: 'rgba(15,118,110,0.04)',
},
statLabel: {
color: colors.textMuted,
@@ -978,6 +1040,8 @@ const styles = StyleSheet.create({
fontSize: 24,
fontWeight: '900',
},
/* ── Review Modal ── */
reviewModal: {
marginTop: 10,
padding: 20,
@@ -1013,6 +1077,8 @@ const styles = StyleSheet.create({
flexDirection: 'row',
gap: 12,
},
/* ── Logout ── */
logoutSection: {
marginTop: 20,
paddingTop: 20,
@@ -1027,7 +1093,7 @@ const styles = StyleSheet.create({
backgroundColor: colors.dangerSoft,
paddingVertical: 14,
paddingHorizontal: 20,
borderRadius: radius.md,
borderRadius: radius.lg,
width: '100%',
borderWidth: 1,
borderColor: 'rgba(239,68,68,0.18)',