import React, { useState, useCallback, useMemo, useEffect } from 'react'; import { View, TouchableOpacity, Alert, RefreshControl, ActivityIndicator, } from 'react-native'; import { useRouter } from 'expo-router'; import MaterialIcons from '@expo/vector-icons/MaterialIcons'; import { AppContainer, MyText, tw, SearchBar, MyFlatList, } from 'common-ui'; import { trpc } from '@/src/trpc-client'; import { formatDistanceToNow } from 'date-fns'; interface User { id: number; name: string | null; mobile: string | null; createdAt: string; totalOrders: number; lastOrderDate: string | null; isSuspended: boolean; } interface UserItemProps { user: User; index: number; onPress: () => void; } const UserItem: React.FC = ({ user, index, onPress }) => { const displayName = user.name || 'Unnamed User'; const hasOrders = user.totalOrders > 0; const lastOrderText = user.lastOrderDate ? formatDistanceToNow(new Date(user.lastOrderDate), { addSuffix: true }) : 'Never'; return ( {/* Left: Index number */} {index + 1} {/* Middle: User Info */} {/* Mobile number - primary identifier */} {user.mobile || 'No Mobile'} {user.isSuspended && ( Suspended )} {/* Name */} {displayName} {/* Registration date */} Registered: {formatDistanceToNow(new Date(user.createdAt), { addSuffix: true })} {/* Right: Order Stats */} {/* Total Orders */} {user.totalOrders} orders {/* Last Order */} Last: {lastOrderText} ); }; interface ListHeaderProps { searchTerm: string; onSearchChange: (text: string) => void; userCount: number; } const ListHeader: React.FC = ({ searchTerm, onSearchChange, userCount }) => ( {/* Search Bar */} {/* Stats Summary */} Showing {userCount} users ); const ListFooter: React.FC<{ isFetching: boolean }> = ({ isFetching }) => { if (!isFetching) return null; return ( ); }; export default function UserManagement() { const router = useRouter(); const [searchTerm, setSearchTerm] = useState(''); const [refreshing, setRefreshing] = useState(false); const [hasLoadedOnce, setHasLoadedOnce] = useState(false); // Infinite scroll query const { data, isLoading, isError, error, fetchNextPage, hasNextPage, isFetchingNextPage, refetch, } = trpc.admin.user.getAllUsers.useInfiniteQuery( { limit: 30, search: searchTerm || undefined, }, { getNextPageParam: (lastPage) => lastPage.nextCursor, } ); useEffect(() => { if (data?.pages?.length) { setHasLoadedOnce(true); } }, [data]); // Flatten all pages and remove duplicates const users = useMemo(() => { const allUsers = data?.pages.flatMap((page) => page.users) || []; // Remove duplicates based on user id const uniqueUsers = allUsers.filter((user, index, self) => index === self.findIndex((u) => u.id === user.id) ); return uniqueUsers; }, [data]); const handleRefresh = useCallback(async () => { setRefreshing(true); await refetch(); setRefreshing(false); }, [refetch]); const handleLoadMore = useCallback(() => { if (hasNextPage && !isFetchingNextPage) { fetchNextPage(); } }, [hasNextPage, isFetchingNextPage, fetchNextPage]); const handleUserPress = useCallback((userId: number) => { router.push(`/(drawer)/user-management/${userId}`); }, [router]); const renderUserItem = useCallback(({ item, index }: { item: User; index: number }) => { return handleUserPress(item.id)} />; }, [handleUserPress]); if (isLoading && !hasLoadedOnce) { return ( Loading users... ); } if (isError) { return ( Error {error?.message || 'Failed to load users'} refetch()} style={tw`mt-6 bg-blue-600 px-6 py-3 rounded-full`} > Retry ); } return ( {/* Users List */} item.id.toString()} onEndReached={handleLoadMore} onEndReachedThreshold={0.5} ListHeaderComponent={ } ListFooterComponent={} refreshControl={ } contentContainerStyle={tw`pb-8`} stickyHeaderIndices={[0]} ListEmptyComponent={ No users found {searchTerm && ( Try adjusting your search )} } /> ); }