167 lines
5.3 KiB
TypeScript
167 lines
5.3 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { View, Text, TouchableOpacity, ActivityIndicator, ScrollView } from 'react-native';
|
|
import { PieChart } from 'react-native-gifted-charts';
|
|
import Icon from 'react-native-vector-icons/Ionicons';
|
|
import { useTheme } from '@theme';
|
|
import { getStyles } from './leadDistribution.styles';
|
|
import { LeadDistributionProps } from './leadDistribution.props';
|
|
|
|
export const LeadDistribution = ({
|
|
data = [],
|
|
loading = false,
|
|
activeAction,
|
|
onActionChange,
|
|
}: LeadDistributionProps) => {
|
|
const { theme: colors, isDark } = useTheme();
|
|
const styles = getStyles(colors, isDark);
|
|
const [menuVisible, setMenuVisible] = useState(false);
|
|
|
|
const actions = [
|
|
{ label: 'Day', value: 'day' },
|
|
{ label: 'Week', value: 'week' },
|
|
{ label: 'Month', value: 'month' },
|
|
{ label: 'Quarter', value: 'quarter' },
|
|
{ label: 'Year', value: 'year' },
|
|
];
|
|
|
|
const currentActionLabel =
|
|
actions.find((a) => a.value === activeAction)?.label || 'Month';
|
|
|
|
const totalLeadItem = data.find((item) => item.name === 'Total Lead');
|
|
const totalCount = totalLeadItem
|
|
? totalLeadItem.count
|
|
: data.reduce((sum, item) => sum + item.count, 0);
|
|
|
|
const displayData = data
|
|
.filter((item) => item.name !== 'Total Lead' && item.count > 0)
|
|
.map((item) => ({
|
|
...item,
|
|
percentage: totalCount > 0 ? Math.round((item.count / totalCount) * 100) : 0,
|
|
}));
|
|
|
|
const pieData = displayData.map((item) => ({
|
|
value: item.count,
|
|
color: item.color,
|
|
}));
|
|
|
|
const renderCenterLabel = () => {
|
|
return (
|
|
<View style={styles.centerLabelContainer}>
|
|
<Text style={styles.centerNumber}>{totalCount}</Text>
|
|
<Text style={styles.centerText}>Total</Text>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<View style={styles.card}>
|
|
{/* Header */}
|
|
<View style={styles.header}>
|
|
<View style={styles.titleRow}>
|
|
<View style={styles.titleIconBadge}>
|
|
<Icon name="pie-chart" size={15} color="#FFFFFF" />
|
|
</View>
|
|
<View>
|
|
<Text style={styles.title}>Lead Distribution</Text>
|
|
<Text style={styles.subtitle}>by status · {currentActionLabel}</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<TouchableOpacity
|
|
style={styles.dropdown}
|
|
onPress={() => setMenuVisible(!menuVisible)}
|
|
activeOpacity={0.7}
|
|
>
|
|
<View style={styles.dropdownDot} />
|
|
<Text style={styles.dropdownText}>{currentActionLabel}</Text>
|
|
<Icon
|
|
name={menuVisible ? 'chevron-up' : 'chevron-down'}
|
|
size={13}
|
|
color={colors.icon}
|
|
/>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{/* Dropdown Menu */}
|
|
{menuVisible && (
|
|
<View style={styles.menuOverlay}>
|
|
{actions.map((act) => {
|
|
const isSelected = act.value === activeAction;
|
|
return (
|
|
<TouchableOpacity
|
|
key={act.value}
|
|
style={[styles.menuItem, isSelected && styles.menuItemActive]}
|
|
onPress={() => {
|
|
onActionChange(act.value);
|
|
setMenuVisible(false);
|
|
}}
|
|
>
|
|
<Text style={[styles.menuItemText, isSelected && styles.menuItemTextActive]}>
|
|
{act.label}
|
|
</Text>
|
|
{isSelected && (
|
|
<Icon name="checkmark" size={14} color={colors.icon} />
|
|
)}
|
|
</TouchableOpacity>
|
|
);
|
|
})}
|
|
</View>
|
|
)}
|
|
|
|
{/* Divider */}
|
|
<View style={styles.divider} />
|
|
|
|
{/* Body — fixed min-height so card never jumps */}
|
|
<View style={styles.body}>
|
|
{loading ? (
|
|
<View style={styles.loaderContainer}>
|
|
<ActivityIndicator size="large" color={colors.icon} />
|
|
</View>
|
|
) : displayData.length === 0 ? (
|
|
<View style={styles.loaderContainer}>
|
|
<Text style={{ color: colors.textSecondary, fontSize: 13 }}>
|
|
No lead data available
|
|
</Text>
|
|
</View>
|
|
) : (
|
|
<>
|
|
<View style={styles.chartContainer}>
|
|
<PieChart
|
|
data={pieData}
|
|
donut
|
|
radius={60}
|
|
innerRadius={45}
|
|
innerCircleColor={colors.card}
|
|
centerLabelComponent={renderCenterLabel}
|
|
/>
|
|
</View>
|
|
|
|
<ScrollView
|
|
style={styles.legendContainer}
|
|
showsVerticalScrollIndicator={false}
|
|
nestedScrollEnabled
|
|
>
|
|
{displayData.map((item, idx) => (
|
|
<View key={idx} style={styles.legendItem}>
|
|
<View style={styles.legendItemLeft}>
|
|
<View style={[styles.bullet, { backgroundColor: item.color }]} />
|
|
<Text style={styles.legendLabel} numberOfLines={1}>
|
|
{item.name}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.legendItemRight}>
|
|
<Text style={styles.legendCount}>{item.count}</Text>
|
|
<Text style={styles.legendPercentage}>{item.percentage}%</Text>
|
|
</View>
|
|
</View>
|
|
))}
|
|
</ScrollView>
|
|
</>
|
|
)}
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
|