562 lines
19 KiB
JavaScript
562 lines
19 KiB
JavaScript
import React, { useContext, useEffect, useState, useCallback } from 'react';
|
||
import { View, Text, StyleSheet, TouchableOpacity, ScrollView, StatusBar, Dimensions, Alert } from 'react-native';
|
||
import { AuthContext } from '../context/AuthContext';
|
||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||
import { useFocusEffect } from '@react-navigation/native';
|
||
import Colors from '../constants/Colors';
|
||
import api from '../services/api';
|
||
import { LogOut, Bell, User } from 'lucide-react-native';
|
||
|
||
const { width } = Dimensions.get('window');
|
||
|
||
const HomeScreen = ({ navigation }) => {
|
||
const { userInfo, logout } = useContext(AuthContext);
|
||
const insets = useSafeAreaInsets();
|
||
const [stats, setStats] = useState({
|
||
pipelineCount: 0,
|
||
monthlyRevenue: 0,
|
||
performance: null,
|
||
target: null,
|
||
overdueCount: 0,
|
||
todayCount: 0,
|
||
newCount: 0
|
||
});
|
||
const [unreadCount, setUnreadCount] = useState(0);
|
||
|
||
const fetchStats = async () => {
|
||
try {
|
||
const response = await api.get('/dashboard/stats');
|
||
if (response.data) {
|
||
setStats({
|
||
pipelineCount: response.data.kpis.pipelineCount,
|
||
monthlyRevenue: response.data.kpis.monthlyRevenue,
|
||
performance: response.data.performance,
|
||
target: response.data.target,
|
||
overdueCount: response.data.kpis.overdueCount,
|
||
todayCount: response.data.kpis.todayCount,
|
||
newCount: response.data.kpis.newCount
|
||
});
|
||
}
|
||
const notifRes = await api.get('/notifications/unread-count');
|
||
setUnreadCount(notifRes.data.count);
|
||
|
||
if (notifRes.data.count > 0) {
|
||
const latestNotifs = await api.get('/notifications/my');
|
||
const performanceAlert = latestNotifs.data.find(n => n.type === 'PERFORMANCE_ALERT' && !n.isRead);
|
||
|
||
if (performanceAlert) {
|
||
Alert.alert(
|
||
"Performance Alert ⚠️",
|
||
performanceAlert.body,
|
||
[{ text: "Check My Performance", onPress: () => navigation.navigate('MyTarget') }]
|
||
);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('Error fetching dashboard stats:', error);
|
||
}
|
||
};
|
||
|
||
useFocusEffect(
|
||
useCallback(() => {
|
||
fetchStats();
|
||
}, [])
|
||
);
|
||
|
||
const StatCard = ({ title, value, color }) => (
|
||
<View style={[styles.statCard, { borderLeftColor: color }]}>
|
||
<Text style={styles.statValue}>{value}</Text>
|
||
<Text style={styles.statLabel}>{title}</Text>
|
||
</View>
|
||
);
|
||
|
||
const MenuCard = ({ title, icon, color, onPress }) => (
|
||
<TouchableOpacity
|
||
style={styles.card}
|
||
onPress={onPress}
|
||
activeOpacity={0.8}
|
||
>
|
||
<View style={[styles.iconContainer, { backgroundColor: color + '15' }]}>
|
||
<Text style={styles.cardIcon}>{icon}</Text>
|
||
</View>
|
||
<Text style={styles.cardTitle}>{title}</Text>
|
||
</TouchableOpacity>
|
||
);
|
||
|
||
const formatCurrency = (value) => {
|
||
if (value >= 1000) {
|
||
return `₹${(value / 1000).toFixed(1)}k`;
|
||
}
|
||
return `₹${value}`;
|
||
};
|
||
|
||
return (
|
||
<View style={styles.container}>
|
||
<StatusBar backgroundColor={Colors.primary} barStyle="light-content" />
|
||
|
||
<ScrollView bounces={false} contentContainerStyle={{ paddingBottom: 40 }}>
|
||
{/* Header Section */}
|
||
<View style={[styles.header, { paddingTop: insets.top + 20 }]}>
|
||
<View style={styles.avatarContainer}>
|
||
<View style={styles.avatar}>
|
||
<Text style={styles.avatarText}>{userInfo?.name?.charAt(0) || 'U'}</Text>
|
||
</View>
|
||
</View>
|
||
<View style={styles.headerTextContainer}>
|
||
<Text style={styles.greeting}>Good morning,</Text>
|
||
<Text style={styles.userName}>{userInfo?.name || 'User'}</Text>
|
||
</View>
|
||
<TouchableOpacity onPress={() => navigation.navigate('MyTarget')} style={styles.bellButton}>
|
||
<Bell size={24} color="white" />
|
||
{unreadCount > 0 && (
|
||
<View style={styles.badge}>
|
||
<Text style={styles.badgeText}>{unreadCount}</Text>
|
||
</View>
|
||
)}
|
||
</TouchableOpacity>
|
||
<TouchableOpacity onPress={() => navigation.navigate('ChangePassword')} style={styles.profileButton}>
|
||
<User size={20} color="white" />
|
||
</TouchableOpacity>
|
||
<TouchableOpacity onPress={logout} style={styles.settingsButton}>
|
||
<LogOut size={20} color="white" />
|
||
</TouchableOpacity>
|
||
</View>
|
||
|
||
{/* Smart Priority Cards */}
|
||
<View style={styles.statsRow}>
|
||
<View style={[styles.priorityCard, { backgroundColor: '#FF6B6B' }]}>
|
||
<Text style={styles.priorityValue}>{stats.overdueCount}</Text>
|
||
<Text style={styles.priorityLabel}>Overdue</Text>
|
||
</View>
|
||
<View style={[styles.priorityCard, { backgroundColor: '#4DABF7' }]}>
|
||
<Text style={styles.priorityValue}>{stats.todayCount}</Text>
|
||
<Text style={styles.priorityLabel}>Today</Text>
|
||
</View>
|
||
<View style={[styles.priorityCard, { backgroundColor: '#51CF66' }]}>
|
||
<Text style={styles.priorityValue}>{stats.newCount}</Text>
|
||
<Text style={styles.priorityLabel}>New</Text>
|
||
</View>
|
||
</View>
|
||
|
||
{/* Score & Target Section */}
|
||
<View style={styles.focusContainer}>
|
||
<View style={styles.scoreBox}>
|
||
<Text style={styles.focusLabel}>PERFORMANCE SCORE</Text>
|
||
<Text style={[styles.scoreText, { color: stats.performance?.score > 80 ? '#27ae60' : stats.performance?.score > 50 ? '#f39c12' : '#e74c3c' }]}>
|
||
{stats.performance ? Math.round(stats.performance.score) : '--'}
|
||
</Text>
|
||
<Text style={styles.tagText}>{stats.performance?.tag.replace('_', ' ') || 'NO DATA'}</Text>
|
||
</View>
|
||
|
||
<TouchableOpacity
|
||
style={styles.targetBox}
|
||
onPress={() => navigation.navigate('MyTarget')}
|
||
activeOpacity={0.8}
|
||
>
|
||
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
|
||
<Text style={[styles.focusLabel, { marginBottom: 0 }]}>MONTHLY TARGET</Text>
|
||
<Text style={{ fontSize: 10 }}>↗️</Text>
|
||
</View>
|
||
{stats.target ? (
|
||
<>
|
||
<View style={styles.progressBarBg}>
|
||
<View style={[styles.progressBarFill, { width: `${Math.min(100, (stats.target.achieved / stats.target.monthly) * 100)}%` }]} />
|
||
</View>
|
||
<View style={styles.targetRow}>
|
||
<Text style={styles.targetValue}>₹{(stats.target.achieved / 1000).toFixed(1)}k</Text>
|
||
<Text style={styles.targetGoal}>/ ₹{(stats.target.monthly / 1000).toFixed(0)}k</Text>
|
||
</View>
|
||
</>
|
||
) : (
|
||
<Text style={styles.noTargetText}>No target assigned</Text>
|
||
)}
|
||
</TouchableOpacity>
|
||
</View>
|
||
|
||
{/* Quick Actions */}
|
||
<View style={styles.quickActionsContainer}>
|
||
<Text style={styles.sectionTitle}>Quick Actions</Text>
|
||
<View style={styles.quickActionsRow}>
|
||
<TouchableOpacity
|
||
style={[styles.quickActionBtn, { backgroundColor: '#eef2ff' }]}
|
||
onPress={() => navigation.navigate('LogActivity', { tab: 'call' })}
|
||
>
|
||
<Text style={styles.quickActionIcon}>📞</Text>
|
||
<Text style={[styles.quickActionLabel, { color: Colors.primary }]}>Log Call</Text>
|
||
</TouchableOpacity>
|
||
<TouchableOpacity
|
||
style={[styles.quickActionBtn, { backgroundColor: '#fdf4ff' }]}
|
||
onPress={() => navigation.navigate('CallLogs')}
|
||
>
|
||
<Text style={styles.quickActionIcon}>📜</Text>
|
||
<Text style={[styles.quickActionLabel, { color: '#c026d3' }]}>Call Logs</Text>
|
||
</TouchableOpacity>
|
||
<TouchableOpacity
|
||
style={[styles.quickActionBtn, { backgroundColor: '#f0fdf4' }]}
|
||
onPress={() => navigation.navigate('LogActivity', { tab: 'followup' })}
|
||
>
|
||
<Text style={styles.quickActionIcon}>📅</Text>
|
||
<Text style={[styles.quickActionLabel, { color: '#16a34a' }]}>Follow-up</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
</View>
|
||
|
||
{/* Dashboard Grid */}
|
||
<View style={styles.gridContainer}>
|
||
<Text style={styles.sectionTitle}>Main Activities</Text>
|
||
<View style={styles.grid}>
|
||
<MenuCard
|
||
title="Attendance"
|
||
icon="📅"
|
||
color={Colors.primary}
|
||
onPress={() => navigation.navigate('Attendance')}
|
||
/>
|
||
<MenuCard
|
||
title="Enquiries"
|
||
icon="📝"
|
||
color={Colors.primary}
|
||
onPress={() => navigation.navigate('EnquiryList')}
|
||
/>
|
||
<MenuCard
|
||
title="Expenses"
|
||
icon="💸"
|
||
color={Colors.primary}
|
||
onPress={() => navigation.navigate('Expense')}
|
||
/>
|
||
<MenuCard
|
||
title="Incentives"
|
||
icon="🏆"
|
||
color={Colors.secondary}
|
||
onPress={() => navigation.navigate('Incentive')}
|
||
/>
|
||
<MenuCard
|
||
title="Marketing"
|
||
icon="📢"
|
||
color={Colors.primary}
|
||
onPress={() => navigation.navigate('LogActivity')}
|
||
/>
|
||
</View>
|
||
</View>
|
||
|
||
{/* Odoo Promo/Tip Section */}
|
||
<View style={styles.tipCard}>
|
||
<View style={styles.tipIconContainer}>
|
||
<Text style={styles.tipIcon}>💡</Text>
|
||
</View>
|
||
<View style={styles.tipTextContainer}>
|
||
<Text style={styles.tipTitle}>Sales Tip</Text>
|
||
<Text style={styles.tipDescription}>Follow up on your proposition deals to increase won rate by 20%.</Text>
|
||
</View>
|
||
</View>
|
||
</ScrollView>
|
||
</View>
|
||
);
|
||
};
|
||
|
||
const styles = StyleSheet.create({
|
||
container: {
|
||
flex: 1,
|
||
backgroundColor: Colors.background,
|
||
},
|
||
header: {
|
||
backgroundColor: Colors.primary,
|
||
paddingBottom: 40,
|
||
paddingHorizontal: 24,
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
},
|
||
avatarContainer: {
|
||
marginRight: 15,
|
||
},
|
||
avatar: {
|
||
width: 50,
|
||
height: 50,
|
||
borderRadius: 25,
|
||
backgroundColor: 'rgba(255,255,255,0.2)',
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
borderWidth: 2,
|
||
borderColor: 'rgba(255,255,255,0.3)',
|
||
},
|
||
avatarText: {
|
||
color: 'white',
|
||
fontSize: 20,
|
||
fontWeight: 'bold',
|
||
},
|
||
headerTextContainer: {
|
||
flex: 1,
|
||
},
|
||
greeting: {
|
||
color: 'rgba(255,255,255,0.7)',
|
||
fontSize: 14,
|
||
},
|
||
userName: {
|
||
color: 'white',
|
||
fontSize: 22,
|
||
fontWeight: 'bold',
|
||
},
|
||
bellButton: {
|
||
padding: 5,
|
||
marginRight: 10,
|
||
position: 'relative',
|
||
},
|
||
badge: {
|
||
position: 'absolute',
|
||
top: 0,
|
||
right: 0,
|
||
backgroundColor: '#ef4444',
|
||
borderRadius: 10,
|
||
minWidth: 16,
|
||
height: 16,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
borderWidth: 1,
|
||
borderColor: Colors.primary,
|
||
},
|
||
badgeText: {
|
||
color: 'white',
|
||
fontSize: 9,
|
||
fontWeight: 'bold',
|
||
paddingHorizontal: 4,
|
||
},
|
||
settingsButton: {
|
||
padding: 5,
|
||
},
|
||
settingsIcon: {
|
||
fontSize: 20,
|
||
},
|
||
profileButton: {
|
||
padding: 5,
|
||
marginRight: 10,
|
||
},
|
||
statsRow: {
|
||
flexDirection: 'row',
|
||
paddingHorizontal: 20,
|
||
marginTop: -25,
|
||
justifyContent: 'space-between',
|
||
},
|
||
priorityCard: {
|
||
width: (width - 60) / 3,
|
||
paddingVertical: 12,
|
||
borderRadius: 12,
|
||
alignItems: 'center',
|
||
shadowColor: '#000',
|
||
shadowOffset: { width: 0, height: 4 },
|
||
shadowOpacity: 0.15,
|
||
shadowRadius: 6,
|
||
elevation: 5,
|
||
},
|
||
priorityValue: {
|
||
color: 'white',
|
||
fontSize: 18,
|
||
fontWeight: 'bold',
|
||
},
|
||
priorityLabel: {
|
||
color: 'rgba(255,255,255,0.8)',
|
||
fontSize: 10,
|
||
fontWeight: 'bold',
|
||
textTransform: 'uppercase',
|
||
},
|
||
focusContainer: {
|
||
flexDirection: 'row',
|
||
paddingHorizontal: 20,
|
||
marginTop: 20,
|
||
justifyContent: 'space-between',
|
||
},
|
||
scoreBox: {
|
||
backgroundColor: 'white',
|
||
width: '40%',
|
||
padding: 16,
|
||
borderRadius: 16,
|
||
alignItems: 'center',
|
||
borderWidth: 1,
|
||
borderColor: Colors.border,
|
||
},
|
||
targetBox: {
|
||
backgroundColor: 'white',
|
||
width: '56%',
|
||
padding: 16,
|
||
borderRadius: 16,
|
||
justifyContent: 'center',
|
||
borderWidth: 1,
|
||
borderColor: Colors.border,
|
||
},
|
||
focusLabel: {
|
||
fontSize: 9,
|
||
fontWeight: '900',
|
||
color: Colors.textMuted,
|
||
letterSpacing: 1,
|
||
marginBottom: 8,
|
||
},
|
||
scoreText: {
|
||
fontSize: 32,
|
||
fontWeight: '900',
|
||
},
|
||
tagText: {
|
||
fontSize: 8,
|
||
fontWeight: 'bold',
|
||
color: Colors.textMuted,
|
||
marginTop: 4,
|
||
},
|
||
progressBarBg: {
|
||
height: 6,
|
||
backgroundColor: '#f1f3f5',
|
||
borderRadius: 3,
|
||
width: '100%',
|
||
overflow: 'hidden',
|
||
},
|
||
progressBarFill: {
|
||
height: '100%',
|
||
backgroundColor: Colors.primary,
|
||
borderRadius: 3,
|
||
},
|
||
targetRow: {
|
||
flexDirection: 'row',
|
||
alignItems: 'baseline',
|
||
marginTop: 8,
|
||
},
|
||
targetValue: {
|
||
fontSize: 14,
|
||
fontWeight: 'bold',
|
||
color: Colors.text,
|
||
},
|
||
targetGoal: {
|
||
fontSize: 10,
|
||
color: Colors.textMuted,
|
||
marginLeft: 2,
|
||
},
|
||
noTargetText: {
|
||
fontSize: 10,
|
||
color: Colors.textMuted,
|
||
fontStyle: 'italic',
|
||
},
|
||
statCard: {
|
||
backgroundColor: 'white',
|
||
width: (width - 56) / 2,
|
||
padding: 16,
|
||
borderRadius: 12,
|
||
borderLeftWidth: 4,
|
||
shadowColor: '#000',
|
||
shadowOffset: { width: 0, height: 2 },
|
||
shadowOpacity: 0.1,
|
||
shadowRadius: 4,
|
||
elevation: 4,
|
||
},
|
||
statValue: {
|
||
fontSize: 18,
|
||
fontWeight: 'bold',
|
||
color: Colors.text,
|
||
},
|
||
statLabel: {
|
||
fontSize: 12,
|
||
color: Colors.textMuted,
|
||
marginTop: 2,
|
||
},
|
||
gridContainer: {
|
||
padding: 20,
|
||
paddingTop: 10,
|
||
},
|
||
quickActionsContainer: {
|
||
paddingHorizontal: 20,
|
||
marginTop: 10,
|
||
},
|
||
quickActionsRow: {
|
||
flexDirection: 'row',
|
||
gap: 12,
|
||
marginTop: 8,
|
||
},
|
||
quickActionBtn: {
|
||
flex: 1,
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
padding: 14,
|
||
borderRadius: 16,
|
||
gap: 10,
|
||
borderWidth: 1,
|
||
borderColor: 'rgba(0,0,0,0.05)',
|
||
},
|
||
quickActionIcon: {
|
||
fontSize: 20,
|
||
},
|
||
quickActionLabel: {
|
||
fontSize: 14,
|
||
fontWeight: '900',
|
||
},
|
||
sectionTitle: {
|
||
fontSize: 14,
|
||
fontWeight: 'bold',
|
||
color: Colors.primary,
|
||
textTransform: 'uppercase',
|
||
letterSpacing: 1,
|
||
marginBottom: 16,
|
||
},
|
||
grid: {
|
||
flexDirection: 'row',
|
||
flexWrap: 'wrap',
|
||
justifyContent: 'space-between',
|
||
},
|
||
card: {
|
||
width: (width - 64) / 2,
|
||
backgroundColor: 'white',
|
||
padding: 20,
|
||
borderRadius: 16,
|
||
marginBottom: 16,
|
||
alignItems: 'center',
|
||
borderWidth: 1,
|
||
borderColor: Colors.border,
|
||
},
|
||
iconContainer: {
|
||
width: 50,
|
||
height: 50,
|
||
borderRadius: 25,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
marginBottom: 12,
|
||
},
|
||
cardIcon: {
|
||
fontSize: 24,
|
||
},
|
||
cardTitle: {
|
||
fontSize: 14,
|
||
fontWeight: 'bold',
|
||
color: Colors.textMuted,
|
||
},
|
||
tipCard: {
|
||
marginHorizontal: 24,
|
||
backgroundColor: Colors.accent,
|
||
borderRadius: 15,
|
||
padding: 16,
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
borderWidth: 1,
|
||
borderColor: `${Colors.secondary}20`,
|
||
},
|
||
tipIconContainer: {
|
||
width: 40,
|
||
height: 40,
|
||
borderRadius: 20,
|
||
backgroundColor: `${Colors.secondary}20`,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
marginRight: 15,
|
||
},
|
||
tipIcon: {
|
||
fontSize: 20,
|
||
},
|
||
tipTextContainer: {
|
||
flex: 1,
|
||
},
|
||
tipTitle: {
|
||
fontSize: 15,
|
||
fontWeight: 'bold',
|
||
color: Colors.secondary,
|
||
},
|
||
tipDescription: {
|
||
fontSize: 12,
|
||
color: '#4a5568',
|
||
lineHeight: 18,
|
||
},
|
||
});
|
||
|
||
export default HomeScreen;
|