175 lines
5.5 KiB
TypeScript
175 lines
5.5 KiB
TypeScript
import React, { useEffect, useMemo, useState } from 'react';
|
|
import { Text, View, FlatList, Alert } from 'react-native';
|
|
import Icon from 'react-native-vector-icons/Ionicons';
|
|
import { useNavigation } from '@react-navigation/native';
|
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
|
import { getStyles } from './customers.styles';
|
|
import { useTheme } from '../../theme';
|
|
import { useAppDispatch, useAppSelector, RootState } from '@store';
|
|
import { getCustomers, deleteCustomer, resetCustomersState } from './thunk';
|
|
import { CustomerItem } from '@interfaces';
|
|
|
|
import { SearchInput, Loader, StatCard, CustomerItemCard } from '@components';
|
|
import { CustomersStackParamList } from '../../navigation/customersStack';
|
|
import { route } from '@utils';
|
|
|
|
type CustomersScreenNavigationProp = NativeStackNavigationProp<
|
|
CustomersStackParamList,
|
|
'customersList'
|
|
>;
|
|
|
|
export const CustomersScreen = () => {
|
|
const { theme: colors } = useTheme();
|
|
const styles = getStyles(colors);
|
|
const dispatch = useAppDispatch();
|
|
const navigation = useNavigation<CustomersScreenNavigationProp>();
|
|
|
|
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [selectedFilter, setSelectedFilter] = useState<'all' | 'active' | 'inactive'>('all');
|
|
|
|
const { items, loading, error, successMessage, deleteError } = useAppSelector(
|
|
(state: RootState) => state.customers,
|
|
);
|
|
|
|
useEffect(() => {
|
|
dispatch(getCustomers());
|
|
}, [dispatch]);
|
|
|
|
useEffect(() => {
|
|
if (successMessage) {
|
|
Alert.alert('Success', successMessage);
|
|
dispatch(resetCustomersState());
|
|
} else if (deleteError) {
|
|
Alert.alert('Error', deleteError);
|
|
dispatch(resetCustomersState());
|
|
}
|
|
}, [successMessage, deleteError, dispatch]);
|
|
|
|
// Stats
|
|
const totalCount = items.length;
|
|
const activeCount = items.filter(c => c.active === '1').length;
|
|
const inactiveCount = items.filter(c => c.active !== '1').length;
|
|
|
|
const statCards = [
|
|
{ label: 'Total', count: totalCount, color: colors.icon, key: 'all' },
|
|
{ label: 'Active', count: activeCount, color: '#10B981', key: 'active' },
|
|
{ label: 'Inactive', count: inactiveCount, color: '#EF4444', key: 'inactive' },
|
|
] as const;
|
|
|
|
// Filtered list
|
|
const filteredCustomers = useMemo(() => {
|
|
return items.filter(c => {
|
|
const matchesSearch = searchTerm
|
|
? c.company?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
c.fullname?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
c.email?.toLowerCase().includes(searchTerm.toLowerCase())
|
|
: true;
|
|
|
|
const matchesFilter =
|
|
selectedFilter === 'all'
|
|
? true
|
|
: selectedFilter === 'active'
|
|
? c.active === '1'
|
|
: c.active !== '1';
|
|
|
|
return matchesSearch && matchesFilter;
|
|
});
|
|
}, [items, searchTerm, selectedFilter]);
|
|
|
|
const handleCustomerPress = (customer: CustomerItem) => {
|
|
navigation.navigate(route.customerDetails, {
|
|
userid: customer.userid,
|
|
customer,
|
|
});
|
|
};
|
|
|
|
const handleDeleteCustomer = (customer: CustomerItem) => {
|
|
Alert.alert(
|
|
'Delete Customer',
|
|
`Are you sure you want to delete "${customer.company || customer.fullname || 'this customer'}"? This action cannot be undone.`,
|
|
[
|
|
{ text: 'Cancel', style: 'cancel' },
|
|
{
|
|
text: 'Delete',
|
|
style: 'destructive',
|
|
onPress: () => {
|
|
dispatch(deleteCustomer({ userid: customer.userid }));
|
|
},
|
|
},
|
|
],
|
|
);
|
|
};
|
|
|
|
const renderCustomer = ({ item }: { item: CustomerItem }) => (
|
|
<CustomerItemCard
|
|
item={item}
|
|
onPress={() => handleCustomerPress(item)}
|
|
onDelete={() => handleDeleteCustomer(item)}
|
|
/>
|
|
);
|
|
|
|
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
{/* Search Header */}
|
|
<View style={styles.header}>
|
|
<SearchInput
|
|
value={searchTerm}
|
|
onChangeText={setSearchTerm}
|
|
placeholder="Search customers..."
|
|
style={styles.searchInput}
|
|
/>
|
|
</View>
|
|
|
|
{/* Stat Cards - Grid aligned across the screen width */}
|
|
<View style={styles.countsContainer}>
|
|
{statCards.map(s => (
|
|
<StatCard
|
|
key={s.key}
|
|
label={s.label}
|
|
count={s.count}
|
|
color={s.color}
|
|
isSelected={selectedFilter === s.key}
|
|
style={styles.statCardFlex}
|
|
onPress={() =>
|
|
setSelectedFilter(prev => (prev === s.key ? 'all' : s.key))
|
|
}
|
|
/>
|
|
))}
|
|
</View>
|
|
|
|
{/* Customer List */}
|
|
{loading ? (
|
|
<Loader message="Loading customers..." />
|
|
) : error ? (
|
|
<View style={styles.errorContainer}>
|
|
<Text style={styles.errorText}>{error}</Text>
|
|
</View>
|
|
) : (
|
|
<FlatList
|
|
data={filteredCustomers}
|
|
keyExtractor={item => item.userid}
|
|
style={{ flex: 1 }}
|
|
contentContainerStyle={styles.listContent}
|
|
renderItem={renderCustomer}
|
|
ListEmptyComponent={() => (
|
|
<View style={styles.emptyContainer}>
|
|
<View style={styles.emptyIconWrap}>
|
|
<Icon name="business-outline" size={32} color={colors.icon} />
|
|
</View>
|
|
<Text style={styles.emptyTitle}>No Customers Found</Text>
|
|
<Text style={styles.emptyText}>
|
|
{searchTerm
|
|
? `No customers match "${searchTerm}". Try a different search.`
|
|
: 'No customers yet. They will appear here.'}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
/>
|
|
)}
|
|
</View>
|
|
);
|
|
};
|