Files
info-target-mobile/screens/e-services/ui/EServicesScreen.tsx
Samandar Turgunboyev d747c72c8d complated
2026-02-17 10:46:57 +05:00

314 lines
9.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// EServicesScreen.tsx
import { useTheme } from '@/components/ThemeContext';
import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query';
import { Image } from 'expo-image';
import { router, useLocalSearchParams } from 'expo-router';
import { ChevronLeft, XIcon } from 'lucide-react-native';
import React, { useCallback, useRef, useState } from 'react';
import {
ActivityIndicator,
Dimensions,
FlatList,
Modal,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { WebView } from 'react-native-webview';
import { useTranslation } from 'react-i18next';
import { RefreshControl } from 'react-native-gesture-handler';
import { eservices_api } from '../lib/api';
const { width: SCREEN_WIDTH } = Dimensions.get('window');
const PAGE_SIZE = 10;
export interface GovermentServiceDataRes {
id: number;
name: string;
url: string;
logo: string;
}
export default function EServicesScreen() {
const { isDark } = useTheme();
const [webUrl, setWebUrl] = useState<string | null>(null);
const [modalVisible, setModalVisible] = useState(false);
const webviewRef = useRef<WebView>(null);
const params = useLocalSearchParams();
const { t } = useTranslation();
const [refreshing, setRefreshing] = useState(false);
const queryClient = useQueryClient();
const { data, isLoading, isError, fetchNextPage, hasNextPage, isFetchingNextPage } =
useInfiniteQuery({
queryKey: ['goverment_service', params.categoryId],
queryFn: async ({ pageParam = 1 }) => {
const response = await eservices_api.list({
page: pageParam,
page_size: PAGE_SIZE,
category: Number(params.categoryId),
});
return response.data.data;
},
getNextPageParam: (lastPage) =>
lastPage.current_page < lastPage.total_pages ? lastPage.current_page + 1 : undefined,
initialPageParam: 1,
});
const services: GovermentServiceDataRes[] = data?.pages.flatMap((p) => p.results) ?? [];
const onRefresh = async () => {
setRefreshing(true);
try {
await queryClient.refetchQueries({ queryKey: ['goverment_service'] });
} finally {
setRefreshing(false);
}
};
const openWebView = (url: string) => {
setWebUrl(url);
setModalVisible(true);
};
const renderItem = useCallback(
({ item }: { item: GovermentServiceDataRes }) => (
<View style={{ alignItems: 'center', marginTop: 12, width: CARD_WIDTH }}>
{/* Logo (bosilganda WebView ochiladi) */}
<TouchableOpacity
style={[
styles.card,
{ backgroundColor: isDark ? '#1e293b' : '#f8fafc' },
isDark ? styles.darkShadow : styles.lightShadow,
]}
onPress={() => openWebView(item.url)}
>
<Image source={{ uri: item.logo }} style={styles.logo} resizeMode="contain" />
</TouchableOpacity>
{/* Name (alogifa, faqat korsatish) */}
<Text
style={[
styles.name,
{ color: isDark ? '#f1f5f9' : '#0f172a', marginTop: 4, paddingHorizontal: 5 },
]}
>
{item.name}
</Text>
</View>
),
[isDark]
);
if (isLoading) {
return (
<View style={[styles.center, { backgroundColor: isDark ? '#0f172a' : '#f8fafc' }]}>
<ActivityIndicator size="large" color="#3b82f6" />
</View>
);
}
if (isError) {
return (
<View style={[styles.center, { backgroundColor: isDark ? '#0f172a' : '#f8fafc' }]}>
<Text style={{ color: 'red' }}>Xatolik yuz berdi</Text>
</View>
);
}
return (
<View style={{ flex: 1, backgroundColor: isDark ? '#0f172a' : '#f8fafc' }}>
{/* Header */}
<View
style={{
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 16,
gap: 10,
paddingVertical: 12,
backgroundColor: isDark ? '#1e293b' : '#f8fafc',
borderBottomWidth: 1,
borderBottomColor: isDark ? '#334155' : '#e2e8f0',
}}
>
<TouchableOpacity
onPress={() => router.back()}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
>
<ChevronLeft size={28} color={isDark ? '#f1f5f9' : '#0f172a'} />
</TouchableOpacity>
<Text
style={{
flex: 1,
fontSize: 18,
fontWeight: '600',
color: isDark ? '#f1f5f9' : '#0f172a',
}}
>
{params.categoryName}
</Text>
</View>
{/* Empty / List */}
{services.length === 0 && !isLoading ? (
<View style={[styles.center, { flex: 1, padding: 16, gap: 5 }]}>
<Text
style={{
color: isDark ? '#f1f5f9' : '#0f172a',
fontSize: 18,
textAlign: 'center',
}}
>
{t("Bu kategoriya bo'yicha xizmat topilmadi")}
</Text>
<Text
style={{
color: isDark ? '#f1f5f9' : '#0f172a',
fontSize: 16,
textAlign: 'center',
}}
>
{t("Tez orada xizmat qo'shiladi")}
</Text>
</View>
) : (
<FlatList
data={services}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
tintColor={isDark ? '#f8fafc' : '#020617'}
colors={['#3b82f6']}
/>
}
keyExtractor={(item) => item.id.toString()}
renderItem={renderItem}
numColumns={3}
columnWrapperStyle={{ justifyContent: 'space-between', marginBottom: 12 }}
contentContainerStyle={{ padding: 16, paddingBottom: 80 }}
onEndReached={() => hasNextPage && !isFetchingNextPage && fetchNextPage()}
onEndReachedThreshold={0.4}
ListFooterComponent={
isFetchingNextPage ? (
<ActivityIndicator color="#3b82f6" size="large" style={{ marginVertical: 20 }} />
) : null
}
showsVerticalScrollIndicator={false}
/>
)}
{/* WebView Modal */}
<Modal visible={modalVisible} animationType="slide">
<SafeAreaView
style={{
flex: 1,
backgroundColor: isDark ? '#0f172a' : '#f8fafc',
}}
>
{/* WebView Header */}
<View
style={{
flexDirection: 'row',
paddingHorizontal: 16,
paddingVertical: 12,
backgroundColor: isDark ? '#1e293b' : '#f8fafc',
alignItems: 'center',
justifyContent: 'space-between',
borderBottomWidth: 1,
borderBottomColor: isDark ? '#334155' : '#e2e8f0',
}}
>
<TouchableOpacity
onPress={() => webviewRef.current?.goBack()}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
>
<ChevronLeft size={28} color={isDark ? '#f1f5f9' : '#0f172a'} />
</TouchableOpacity>
<Text
style={{
flex: 1,
textAlign: 'center',
fontSize: 16,
fontWeight: '600',
color: isDark ? '#f1f5f9' : '#0f172a',
}}
numberOfLines={1}
>
{webUrl}
</Text>
<TouchableOpacity
onPress={() => setModalVisible(false)}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
>
<XIcon size={28} color={isDark ? '#f1f5f9' : '#0f172a'} />
</TouchableOpacity>
</View>
{/* WebView */}
{webUrl && (
<WebView
ref={webviewRef}
originWhitelist={['*']}
javaScriptEnabled
domStorageEnabled
onShouldStartLoadWithRequest={(request) => {
// iOS va Android uchun barcha URLlarni WebView ichida ochish
return true;
}}
source={{ uri: webUrl }}
style={{ flex: 1, backgroundColor: isDark ? '#0f172a' : '#f8fafc' }}
startInLoadingState
renderLoading={() => (
<ActivityIndicator color="#3b82f6" size="large" style={{ flex: 1 }} />
)}
/>
)}
</SafeAreaView>
</Modal>
</View>
);
}
const CARD_WIDTH = (SCREEN_WIDTH - 50) / 3;
const styles = StyleSheet.create({
center: { flex: 1, justifyContent: 'center', alignItems: 'center' },
card: {
width: CARD_WIDTH,
borderRadius: 12,
padding: 12,
alignItems: 'center',
},
logo: {
width: 80,
height: 80,
marginBottom: 8,
},
name: {
fontSize: 14,
fontWeight: '600',
textAlign: 'center',
},
darkShadow: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.4,
shadowRadius: 6,
elevation: 3,
},
lightShadow: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 2,
},
});