fitst commit
This commit is contained in:
199
screens/profile/ui/EmployeesTab.tsx
Normal file
199
screens/profile/ui/EmployeesTab.tsx
Normal file
@@ -0,0 +1,199 @@
|
||||
import { useTheme } from '@/components/ThemeContext';
|
||||
import { formatNumber } from '@/constants/formatPhone';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { ArrowLeft, Plus, User } from 'lucide-react-native';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
FlatList,
|
||||
Pressable,
|
||||
RefreshControl,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { user_api } from '../lib/api';
|
||||
import { ExployeesDataResponse } from '../lib/type';
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
|
||||
export function EmployeesTab() {
|
||||
const router = useRouter();
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const { isDark } = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const theme = {
|
||||
background: isDark ? '#0f172a' : '#f8fafc',
|
||||
cardBg: isDark ? '#1e293b' : '#ffffff',
|
||||
text: isDark ? '#f8fafc' : '#0f172a',
|
||||
textSecondary: isDark ? '#94a3b8' : '#64748b',
|
||||
iconBg: isDark ? '#1e40af15' : '#dbeafe',
|
||||
primary: '#3b82f6',
|
||||
emptyIcon: isDark ? '#334155' : '#cbd5e1',
|
||||
};
|
||||
|
||||
const { data, isLoading, isError, fetchNextPage, hasNextPage, isFetchingNextPage, refetch } =
|
||||
useInfiniteQuery({
|
||||
queryKey: ['employees_list'],
|
||||
queryFn: async ({ pageParam = 1 }) => {
|
||||
const response = await user_api.employess({
|
||||
page: pageParam,
|
||||
page_size: PAGE_SIZE,
|
||||
});
|
||||
return response.data.data;
|
||||
},
|
||||
getNextPageParam: (lastPage) =>
|
||||
lastPage && lastPage.current_page && lastPage.total_pages
|
||||
? lastPage.current_page < lastPage.total_pages
|
||||
? lastPage.current_page + 1
|
||||
: undefined
|
||||
: undefined,
|
||||
initialPageParam: 1,
|
||||
});
|
||||
|
||||
const allEmployees = data?.pages.flatMap((p) => p.results) ?? [];
|
||||
|
||||
const renderItem = useCallback(
|
||||
({ item }: { item: ExployeesDataResponse }) => (
|
||||
<View style={[styles.card, { backgroundColor: theme.cardBg }]}>
|
||||
<View style={[styles.iconContainer, { backgroundColor: theme.iconBg }]}>
|
||||
<User size={24} color={theme.primary} />
|
||||
</View>
|
||||
<View style={styles.infoContainer}>
|
||||
<Text style={[styles.name, { color: theme.text }]}>
|
||||
{item.first_name} {item.last_name}
|
||||
</Text>
|
||||
<Text style={[styles.phone, { color: theme.textSecondary }]}>
|
||||
{formatNumber(item.phone)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
),
|
||||
[theme]
|
||||
);
|
||||
|
||||
const onRefresh = async () => {
|
||||
setRefreshing(true);
|
||||
await refetch();
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: theme.background }]}>
|
||||
<View style={styles.topHeader}>
|
||||
<Pressable onPress={() => router.push('/profile')}>
|
||||
<ArrowLeft color={theme.text} />
|
||||
</Pressable>
|
||||
<Text style={[styles.headerTitle, { color: theme.text }]}>{t('Xodimlar')}</Text>
|
||||
<Pressable onPress={() => router.push('/profile/employees/add')}>
|
||||
<Plus color={theme.primary} />
|
||||
</Pressable>
|
||||
</View>
|
||||
<ActivityIndicator size={'large'} />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: theme.background }]}>
|
||||
<Text style={{ color: 'red', marginTop: 50, textAlign: 'center' }}>
|
||||
{t('Xatolik yuz berdi')}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: theme.background }]}>
|
||||
<View style={styles.topHeader}>
|
||||
<Pressable onPress={() => router.push('/profile')}>
|
||||
<ArrowLeft color={theme.text} />
|
||||
</Pressable>
|
||||
<Text style={[styles.headerTitle, { color: theme.text }]}>{t('Xodimlar')}</Text>
|
||||
<Pressable onPress={() => router.push('/profile/employees/add')}>
|
||||
<Plus color={theme.primary} />
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<FlatList
|
||||
data={allEmployees}
|
||||
keyExtractor={(item) => item.phone}
|
||||
renderItem={renderItem}
|
||||
contentContainerStyle={styles.list}
|
||||
onEndReached={() => {
|
||||
if (hasNextPage && !isFetchingNextPage) {
|
||||
fetchNextPage();
|
||||
}
|
||||
}}
|
||||
onEndReachedThreshold={0.5}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={theme.primary} />
|
||||
}
|
||||
ListFooterComponent={isFetchingNextPage ? <ActivityIndicator size={'large'} /> : null}
|
||||
ListEmptyComponent={
|
||||
<View style={styles.emptyContainer}>
|
||||
<User size={64} color={theme.emptyIcon} />
|
||||
<Text style={[styles.emptyText, { color: theme.textSecondary }]}>
|
||||
{t("Hozircha xodimlar yo'q")}
|
||||
</Text>
|
||||
<Pressable
|
||||
style={[styles.emptyButton, { backgroundColor: theme.primary }]}
|
||||
onPress={() => router.push('/profile/employees/add')}
|
||||
>
|
||||
<Plus size={20} color="#fff" />
|
||||
<Text style={styles.emptyButtonText}>{t("Xodim qo'shish")}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1 },
|
||||
addButton: { padding: 8 },
|
||||
list: { padding: 16, gap: 12 },
|
||||
card: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
borderRadius: 16,
|
||||
padding: 16,
|
||||
gap: 12,
|
||||
},
|
||||
iconContainer: {
|
||||
width: 56,
|
||||
height: 56,
|
||||
borderRadius: 28,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
infoContainer: { flex: 1, gap: 4 },
|
||||
name: { fontSize: 17, fontWeight: '700' },
|
||||
phone: { fontSize: 15, fontWeight: '500' },
|
||||
emptyContainer: { alignItems: 'center', justifyContent: 'center', paddingVertical: 80, gap: 16 },
|
||||
emptyText: { fontSize: 17, fontWeight: '600' },
|
||||
emptyButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
paddingHorizontal: 24,
|
||||
paddingVertical: 12,
|
||||
borderRadius: 12,
|
||||
marginTop: 8,
|
||||
},
|
||||
emptyButtonText: { fontSize: 16, fontWeight: '600', color: '#fff' },
|
||||
topHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
padding: 16,
|
||||
alignItems: 'center',
|
||||
},
|
||||
headerTitle: { fontSize: 18, fontWeight: '700' },
|
||||
});
|
||||
Reference in New Issue
Block a user