Files
info-target-mobile/screens/e-services/ui/EServicesScreen.tsx
Samandar Turgunboyev ab363ca3b9 bug fixed
2026-03-02 13:22:55 +05:00

276 lines
7.0 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.

import { useTheme } from "@/components/ThemeContext";
import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
import { Image } from "expo-image";
import { router, useLocalSearchParams } from "expo-router";
import * as WebBrowser from "expo-web-browser";
import { ChevronLeft } from "lucide-react-native";
import { useCallback, useState } from "react";
import {
ActivityIndicator,
Dimensions,
FlatList,
StyleSheet,
Text,
TouchableOpacity,
View
} from "react-native";
import { useTranslation } from "react-i18next";
import { RefreshControl } from "react-native-gesture-handler";
import { Toast } from "toastify-react-native";
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 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 handleOpenBrowser = async (fileUrl: string) => {
try {
await WebBrowser.openBrowserAsync(fileUrl);
} catch (error) {
Toast.error(t("Xatolik yuz berdi"));
}
};
const renderItem = useCallback(
({ item }: { item: GovermentServiceDataRes }) => (
<View style={{ alignItems: "center", marginTop: 12, width: CARD_WIDTH }}>
<TouchableOpacity
style={[
styles.card,
{ backgroundColor: isDark ? "#1e293b" : "#f8fafc" },
isDark ? styles.darkShadow : styles.lightShadow,
]}
onPress={() => handleOpenBrowser(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}
/>
)}
</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,
},
});