Initial commit

This commit is contained in:
Samandar Turgunboyev
2025-08-26 16:26:59 +05:00
commit fd95422447
318 changed files with 38301 additions and 0 deletions

View File

@@ -0,0 +1,116 @@
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { PacketsData } from 'api/packets';
import React, { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Text, TouchableOpacity, View } from 'react-native';
import { PaymentStyle } from './style';
type WalletStackParamList = {
PaymentMethod: { packets: PacketsData };
PaymentQrCode: { packets: PacketsData };
};
type LoginScreenNavigationProp =
NativeStackNavigationProp<WalletStackParamList>;
interface Props {
packets: PacketsData;
}
const Payment = ({ packets }: Props) => {
const navigation = useNavigation<LoginScreenNavigationProp>();
const { t } = useTranslation();
const handlePaymentPress = useCallback(
(item: any) => {
const isPaid = item.paymentStatus === 'paid';
navigation.navigate(isPaid ? 'PaymentQrCode' : 'PaymentMethod', {
packets: item, // tanlangan itemni toliq yuboramiz
});
},
[navigation],
);
const cardContainerStyle = useMemo(
() => ({
flexDirection: 'row' as const,
gap: 10,
justifyContent: 'center' as const,
alignItems: 'center' as const,
}),
[],
);
const badgeStyle = useMemo(
() => [PaymentStyle.badge, { backgroundColor: '#D32F2F' }],
[],
);
const renderPaymentCard = useCallback(
(item: any) => {
const isPaid = item.paymentStatus === 'paid';
const cardStyle = [
PaymentStyle.card,
{ borderColor: isPaid ? '#4CAF50' : '#D32F2F', borderWidth: 1.5 },
];
return (
<TouchableOpacity
activeOpacity={0.7}
onPress={() => handlePaymentPress(item)}
key={item.id}
>
<View style={cardContainerStyle}>
<View style={cardStyle}>
<View style={PaymentStyle.cardHeader}>
<Text style={PaymentStyle.title}>{item.packetName}</Text>
{isPaid ? (
<Text style={PaymentStyle.badge}>{t("To'langan")}</Text>
) : (
<Text style={badgeStyle}>{t("To'lanmagan")}</Text>
)}
</View>
<View style={PaymentStyle.row}>
<Text style={PaymentStyle.infoTitle}>{t('Reys raqami')}</Text>
<Text
style={[
PaymentStyle.text,
{ width: '60%', textAlign: 'right' },
]}
>
{item.packetName}
</Text>
</View>
<View style={PaymentStyle.row}>
<Text style={PaymentStyle.infoTitle}>
{t("Mahsulotlar og'irligi")}
</Text>
<Text style={PaymentStyle.text}>{item.weight}</Text>
</View>
<View style={PaymentStyle.row}>
<Text style={PaymentStyle.infoTitle}>
{t('Mahsulotlar soni')}
</Text>
<Text style={PaymentStyle.text}>{item.items.length}</Text>
</View>
<View style={PaymentStyle.row}>
<Text style={PaymentStyle.infoTitle}>{t('Umumiy narxi')}</Text>
<Text style={PaymentStyle.text}>{item.totalPrice}</Text>
</View>
</View>
</View>
</TouchableOpacity>
);
},
[handlePaymentPress, cardContainerStyle, badgeStyle, t],
);
return (
<View style={PaymentStyle.container}>
{packets?.data.map(renderPaymentCard)}
</View>
);
};
export default Payment;

View File

@@ -0,0 +1,268 @@
import { useQuery } from '@tanstack/react-query';
import packetsApi from 'api/packets';
import LoadingScreen from 'components/LoadingScreen';
import Navbar from 'components/Navbar';
import Navigation from 'components/Navigation';
import NoResult from 'components/NoResult';
import Pagination from 'components/Pagination';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
RefreshControl,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import Payment from './Payment';
const Wallet = () => {
const { t } = useTranslation();
const [refreshing, setRefreshing] = useState(false);
const [page, setPage] = useState(0);
const [pageAvia, setPageAvia] = useState(0);
const [selectedType, setSelectedType] = useState<'AVIA' | 'AUTO'>('AVIA');
const {
data: packets,
isFetching,
isLoading,
isError,
refetch,
} = useQuery({
queryKey: ['packets', page],
queryFn: () =>
packetsApi.getPackets({
page,
size: 10,
cargoType: 'AUTO',
direction: 'ASC',
sort: 'id',
}),
});
const {
data: packetsAvia,
isFetching: isFetchingAvia,
isLoading: isLoadingAvia,
isError: isErrorAvia,
refetch: refetchAvia,
} = useQuery({
queryKey: ['packetsAvia', pageAvia],
queryFn: () =>
packetsApi.getPackets({
page: pageAvia,
size: 10,
cargoType: 'AVIA',
direction: 'ASC',
sort: 'id',
}),
});
useEffect(() => {
const loadInitialData = async () => {
try {
await new Promise(resolve => setTimeout(resolve, 1000));
refetch();
refetchAvia();
} catch (error) {
console.error('Wallet loading error:', error);
refetch();
refetchAvia();
}
};
loadInitialData();
}, []);
const onRefresh = useCallback(async () => {
try {
setRefreshing(true);
await refetch();
} catch (error) {
console.error('Refresh error:', error);
} finally {
setRefreshing(false);
}
}, [refetch]);
const refreshControl = useMemo(
() => <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />,
[refreshing, onRefresh],
);
if (isLoading || isFetching || isLoadingAvia || isFetchingAvia) {
return (
<SafeAreaView style={{ flex: 1 }}>
<View style={styles.container}>
<Navbar />
<LoadingScreen />
<Navigation />
</View>
</SafeAreaView>
);
}
if (isError || isErrorAvia) {
return (
<SafeAreaView style={{ flex: 1 }}>
<View style={styles.container}>
<Navbar />
<NoResult message="Xatolik yuz berdi" />
<Navigation />
</View>
</SafeAreaView>
);
}
if (
(packets && !(packets?.data.length > 0)) ||
(packetsAvia && !(packetsAvia?.data.length > 0))
) {
return (
<SafeAreaView style={{ flex: 1 }}>
<Navbar />
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.title}>{t("To'lov")}</Text>
</View>
<NoResult />
</View>
<Navigation />
</SafeAreaView>
);
}
return (
<SafeAreaView style={{ flex: 1 }}>
<View style={styles.container}>
<Navbar />
<ScrollView
refreshControl={refreshControl}
style={{ flex: 1 }}
contentContainerStyle={{ flexGrow: 1 }}
removeClippedSubviews={true}
keyboardShouldPersistTaps="handled"
>
<View style={{ flex: 1, justifyContent: 'space-between' }}>
<View>
<View style={styles.header}>
<Text style={styles.title}>{t("To'lov")}</Text>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<TouchableOpacity
style={{
padding: 5,
backgroundColor:
selectedType === 'AVIA' ? '#28A7E8' : '#F3FAFF',
borderTopLeftRadius: 8,
borderBottomLeftRadius: 8,
}}
onPress={() => setSelectedType('AVIA')}
>
<Text
style={{
color: selectedType === 'AVIA' ? '#fff' : '#28A7E8',
fontSize: 14,
}}
>
AVIA
</Text>
</TouchableOpacity>
<View style={{ width: 1 }} />
<TouchableOpacity
style={{
padding: 5,
backgroundColor:
selectedType === 'AUTO' ? '#28A7E8' : '#F3FAFF',
borderTopRightRadius: 8,
borderBottomRightRadius: 8,
}}
onPress={() => setSelectedType('AUTO')}
>
<Text
style={{
color: selectedType === 'AUTO' ? '#fff' : '#28A7E8',
fontSize: 14,
fontWeight: '500',
}}
>
AUTO
</Text>
</TouchableOpacity>
</View>
</View>
<Payment
packets={selectedType === 'AUTO' ? packets! : packetsAvia!}
/>
</View>
<View
style={{
flexDirection: 'row',
justifyContent: 'flex-end',
width: '95%',
alignSelf: 'center',
paddingRight: 20,
paddingVertical: 10,
}}
>
<Pagination
page={selectedType === 'AUTO' ? page : pageAvia}
totalPages={
selectedType === 'AUTO'
? packets?.totalPages ?? 1
: packetsAvia?.totalPages ?? 1
}
setPage={selectedType === 'AUTO' ? setPage : setPageAvia}
/>
</View>
</View>
</ScrollView>
<Navigation />
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
header: {
paddingHorizontal: 20,
paddingTop: 5,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#333',
},
cards: {
flexDirection: 'row',
justifyContent: 'space-around',
paddingHorizontal: 20,
paddingBottom: 10,
},
card: {
alignItems: 'center',
justifyContent: 'center',
width: 100,
height: 100,
backgroundColor: '#f0f0f0',
borderRadius: 10,
padding: 10,
},
cardText: {
marginTop: 5,
fontSize: 12,
color: '#555',
textAlign: 'center',
},
});
export default Wallet;

View File

@@ -0,0 +1,268 @@
import { StyleSheet } from 'react-native';
export const PaymentStyle = StyleSheet.create({
container: {
flex: 1,
width: '95%',
margin: 'auto',
marginTop: 5,
gap: 10,
},
card: {
padding: 20,
width: '95%',
backgroundColor: '#FFFFFF',
borderRadius: 8,
gap: 10,
},
cardHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
title: {
width: '60%',
fontSize: 18,
fontWeight: '600',
},
badge: {
backgroundColor: '#4CAF50',
color: '#fff',
fontSize: 12,
paddingHorizontal: 8,
paddingVertical: 2,
borderRadius: 5,
overflow: 'hidden',
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
},
infoTitle: {
fontWeight: '500',
fontSize: 14,
color: '#979797',
},
text: {
color: '#28A7E8',
fontWeight: '500',
fontSize: 16,
},
select: {
backgroundColor: '#FFFFFF',
height: 25,
width: 25,
borderWidth: 1,
borderRadius: 4,
alignItems: 'center',
justifyContent: 'center',
},
button: {
position: 'absolute',
bottom: 70,
left: 20,
right: 20,
height: 56,
borderRadius: 8,
justifyContent: 'center',
backgroundColor: '#28A7E8',
zIndex: 10,
},
modalBtn: {
position: 'absolute',
height: 56,
width: '100%',
borderRadius: 8,
justifyContent: 'center',
backgroundColor: '#28A7E8',
bottom: 10,
right: 20,
},
btnText: {
color: '#fff',
fontSize: 20,
textAlign: 'center',
},
containerMethod: {
width: '95%',
alignSelf: 'center',
marginTop: 20,
},
cardMethod: {
backgroundColor: '#FFFFFF',
borderRadius: 8,
},
planeContainer: {
alignItems: 'center',
marginBottom: 20,
},
planeIcon: {
position: 'absolute',
top: 10,
left: 85,
},
infoCard: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
paddingLeft: 20,
paddingRight: 20,
marginBottom: 20,
},
info: {
marginBottom: 15,
gap: 5,
},
titleMethod: {
color: '#1D1D1D',
fontSize: 18,
fontWeight: '500',
},
textMethod: {
color: '#555555',
fontSize: 16,
fontWeight: '400',
},
receiptCard: {
borderRadius: 10,
padding: 16,
marginBottom: 15,
gap: 8,
},
rowBetween: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
rowRight: {
flexDirection: 'row',
justifyContent: 'flex-end',
},
itemName: {
fontSize: 17,
fontWeight: '600',
color: '#333',
},
weight: {
fontSize: 16,
color: '#777',
},
track: {
fontSize: 15,
color: '#666',
},
price: {
fontSize: 15,
fontWeight: '500',
color: '#444',
},
total: {
fontSize: 16,
fontWeight: '600',
color: '#1D1D1D',
},
paymentCard: {
flexDirection: 'row',
alignItems: 'center',
gap: 10,
shadowOpacity: 0.3,
shadowRadius: 6,
shadowOffset: { width: 0, height: 2 },
},
modalOverlay: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.4)',
justifyContent: 'center',
alignItems: 'center',
},
modalContent: {
width: '90%',
maxHeight: '80%',
backgroundColor: '#fff',
borderRadius: 10,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.3,
shadowRadius: 4,
elevation: 5,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 20,
},
content: {
padding: 10,
margin: 'auto',
justifyContent: 'center',
alignItems: 'center',
gap: 10,
marginBottom: 20,
},
circle: {
backgroundColor: '#28A7E8',
width: 60,
height: 60,
borderRadius: 120,
justifyContent: 'center',
alignItems: 'center',
},
titleModal: {
fontSize: 16,
fontWeight: '500',
},
closeBtn: {
padding: 5,
width: 30,
height: 30,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#0000000D',
borderRadius: 50,
shadowColor: '#FFFFFF40',
shadowOpacity: 0.3,
shadowRadius: 6,
shadowOffset: { width: 0, height: 2 },
},
divider: {
width: '100%',
height: 1,
backgroundColor: '#E0E0E0',
marginBottom: 10,
marginHorizontal: 0,
},
status: {
color: '#00000099',
fontSize: 16,
fontWeight: '500',
},
label: {
fontSize: 12,
fontWeight: '500',
color: '#28A7E8',
marginBottom: 5,
},
errorText: {
color: 'red',
fontSize: 12,
},
input: {
borderWidth: 1,
borderColor: '#D8DADC',
borderRadius: 12,
padding: 15,
fontSize: 16,
fontWeight: '400',
backgroundColor: '#FFFFFF',
color: '#000000',
},
});