card order ready

This commit is contained in:
2025-07-09 11:46:20 +05:00
parent 6044d5ae78
commit 4dce2a6d63
12 changed files with 538 additions and 16 deletions

View File

@@ -0,0 +1,17 @@
# 0.0.1
- Initial release
# 0.0.2
- Commented Visa/Master and Sber cards
```js
{
title: 'Halkara tölegler',
items: [
{ id: 9, title: 'Visa/Master tölegleri (talyplar üçin)', icon: 'logo-usd', description: 'Visa/Master' },
{ id: 10, title: 'Sber tölegler (talyplar üçin)', icon: 'globe', description: 'Sber tölegler' },
],
},
```

16
package-lock.json generated
View File

@@ -23,7 +23,8 @@
"react-native-modal-datetime-picker": "^15.0.1",
"react-native-safe-area-context": "^5.5.1",
"react-native-screens": "^4.11.1",
"react-native-svg": "^15.12.0"
"react-native-svg": "^15.12.0",
"react-native-webview": "^13.11.0"
},
"devDependencies": {
"@babel/core": "^7.20.0"
@@ -6467,6 +6468,19 @@
"react-native": "*"
}
},
"node_modules/react-native-webview": {
"version": "13.15.0",
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.15.0.tgz",
"integrity": "sha512-Vzjgy8mmxa/JO6l5KZrsTC7YemSdq+qB01diA0FqjUTaWGAGwuykpJ73MDj3+mzBSlaDxAEugHzTtkUQkQEQeQ==",
"dependencies": {
"escape-string-regexp": "^4.0.0",
"invariant": "2.2.4"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native/node_modules/@react-native/virtualized-lists": {
"version": "0.79.5",
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.79.5.tgz",

View File

@@ -24,7 +24,8 @@
"react-native-safe-area-context": "^5.5.1",
"react-native-screens": "^4.11.1",
"react-native-svg": "^15.12.0",
"expo-image-picker": "~16.1.4"
"expo-image-picker": "~16.1.4",
"react-native-webview": "^13.11.0"
},
"devDependencies": {
"@babel/core": "^7.20.0"

View File

@@ -21,6 +21,9 @@ import CardRequisiteOrderDetailsScreen from '../screens/Card/CardRequisiteOrderD
import CardPinOrdersScreen from '../screens/Card/CardPinOrdersScreen';
import CreateCardPinOrderScreen from '../screens/Card/CreateCardPinOrderScreen';
import CardPinOrderDetailsScreen from '../screens/Card/CardPinOrderDetailsScreen';
import CardOrdersScreen from '../screens/Card/CardOrdersScreen';
import CreateCardOrderScreen from '../screens/Card/CreateCardOrderScreen';
import CardOrderDetailsScreen from '../screens/Card/CardOrderDetailsScreen';
const Stack = createStackNavigator();
@@ -47,6 +50,9 @@ const MenuNavigator = () => (
<Stack.Screen name="CardPinOrders" component={CardPinOrdersScreen} />
<Stack.Screen name="CreateCardPinOrder" component={CreateCardPinOrderScreen} />
<Stack.Screen name="CardPinOrderDetails" component={CardPinOrderDetailsScreen} />
<Stack.Screen name="CardOrders" component={CardOrdersScreen} />
<Stack.Screen name="CreateCardOrder" component={CreateCardOrderScreen} />
<Stack.Screen name="CardOrderDetails" component={CardOrderDetailsScreen} />
</Stack.Navigator>
);

View File

@@ -0,0 +1,133 @@
import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, ScrollView, SafeAreaView, Image, Alert, Linking } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { useNavigation, useRoute } from '@react-navigation/native';
import { StatusBar } from 'expo-status-bar';
import apiService from '../../services/apiService';
import { COLORS } from '../../constants/colors';
import { useBaseEnums } from '../../contexts/BaseEnumsContext';
const DetailRow = ({ label, value, showBorder=true })=> (
<View style={[styles.detailRow, showBorder && styles.detailRowBorder]}>
<Text style={styles.detailKey}>{label}</Text>
<Text style={styles.detailValue}>{String(value)}</Text>
</View>
);
const CardOrderDetailsScreen = ()=>{
const navigation = useNavigation();
const { params } = useRoute();
const orderId = params?.orderId;
const { getLabel, getBranches } = useBaseEnums();
const [loading,setLoading] = useState(true);
const [order,setOrder] = useState(null);
const [branchName,setBranchName]=useState('');
const fetchData = async()=>{
setLoading(true);
const res = await apiService.getCardOrder(orderId);
if(res.success){
setOrder(res.data);
if(res.data?.region && res.data?.branch_id){
try{ const brs = await getBranches(res.data.region); const found = brs.find(b=>String(b.id)===String(res.data.branch_id)); if(found) setBranchName(found.name);}catch(e){}
}
}else{ Alert.alert('Ýalňyşlyk', res.error || 'Maglumat almak bolmady'); }
setLoading(false);
};
useEffect(()=>{ fetchData(); },[]);
const handleDelete = ()=>{ Alert.alert('Tassykla','Pozmakçy my?',[{text:'Goýbolsun',style:'cancel'},{text:'Poz',style:'destructive',onPress:async()=>{
const r = await apiService.deleteCardOrder(orderId);
if(r.success){ Alert.alert('Pozuldy',r.message||'Pozuldy',[{text:'OK',onPress:()=>navigation.goBack()}]); } else { Alert.alert('Ýalňyşlyk', r.error || 'Pozup bolmady'); }
}}]); };
if(loading) return <View style={styles.center}><ActivityIndicator size="large" color={COLORS.primary}/></View>;
if(!order) return <View style={styles.center}><Text>Maglumat ýok</Text></View>;
const formattedDate = (d)=> d? new Date(d).toLocaleDateString():'';
return (
<SafeAreaView style={styles.container}>
<StatusBar style="dark" />
<TouchableOpacity style={styles.backBtn} onPress={()=>navigation.goBack()}><Ionicons name="close" size={28} color={COLORS.textPrimary}/></TouchableOpacity>
<ScrollView contentContainerStyle={{paddingBottom:120,paddingHorizontal:24}}>
<Text style={styles.title}>Täze kart sargyt</Text>
{/* Summary */}
<View style={styles.detailCard}>
<DetailRow label="ID" value={order.unique_id} />
<DetailRow label="Kartyň çykarylmagynyň sebäbi" value={getLabel('card_states', order.card_state_id)} />
<DetailRow label="Kart görnüşi" value={getLabel('card_types', order.card_type_id)} />
<DetailRow label="Tölenildi" value={order.paid? 'Hawa':'Ýok'} showBorder={false}/>
</View>
{/* Location */}
{(order.region || branchName) && (
<>
<Text style={styles.sectionTitle}>Lokasiýa</Text>
<View style={styles.detailCard}>
{order.region && <DetailRow label="Welaýat" value={getLabel('regions', order.region)} />}
{(branchName||order.branch_id) && <DetailRow label="Şahamça" value={branchName || order.branch_id} showBorder={false}/>}
</View>
</>
)}
{/* Personal */}
<Text style={styles.sectionTitle}>Şahsy maglumatlar</Text>
<View style={styles.detailCard}>
<DetailRow label="Ady" value={order.customer_name} />
<DetailRow label="Familiýasy" value={order.customer_surname} />
{order.customer_patronic_name && <DetailRow label="Atasynyň ady" value={order.customer_patronic_name} />}
{order.old_surname && <DetailRow label="Köne familiýasy" value={order.old_surname} />}
<DetailRow label="Doglan güni" value={formattedDate(order.born_at)} />
<DetailRow label="Telefon" value={`+993 ${order.phone}`} />
{order.phone_additional && <DetailRow label="Telefon goşmaça" value={`+993 ${order.phone_additional}`}/>}
<DetailRow label="Ýazgy edilen salgyňyz" value={order.passport_address} />
<DetailRow label="Häzirki ýaşaýyş ýeri" value={order.real_address} />
<DetailRow label="Işleýän ýeriňiz we wezipäňiz" value={order.job_location} showBorder={false}/>
</View>
{/* Passport info */}
<Text style={styles.sectionTitle}>Pasport</Text>
<View style={styles.detailCard}>
<DetailRow label="Seriýa/Belgi" value={`${order.passport_serie} ${order.passport_id}`} />
<DetailRow label="Berlen senesi" value={formattedDate(order.passport_given_at)} />
<DetailRow label="Berlen ýeri" value={order.passport_given_by} />
<DetailRow label="Doglan ýeri (passport)" value={order.born_place} style={{marginBottom:12}} showBorder={false}/>
<View style={styles.imageGrid}>
{order.passport_one && <TouchableOpacity onPress={()=>Linking.openURL(order.passport_one)} style={styles.imageWrapper}><Image source={{uri:order.passport_one}} style={styles.img}/><Text style={styles.imageLabel}>1-nji sah.</Text></TouchableOpacity>}
{order.passport_two && <TouchableOpacity onPress={()=>Linking.openURL(order.passport_two)} style={styles.imageWrapper}><Image source={{uri:order.passport_two}} style={styles.img}/><Text style={styles.imageLabel}>2-3 sah.</Text></TouchableOpacity>}
{order.passport_three && <TouchableOpacity onPress={()=>Linking.openURL(order.passport_three)} style={styles.imageWrapper}><Image source={{uri:order.passport_three}} style={styles.img}/><Text style={styles.imageLabel}>8-9 sah.</Text></TouchableOpacity>}
{order.passport_four && <TouchableOpacity onPress={()=>Linking.openURL(order.passport_four)} style={styles.imageWrapper}><Image source={{uri:order.passport_four}} style={styles.img}/><Text style={styles.imageLabel}>32 sah.</Text></TouchableOpacity>}
</View>
</View>
<TouchableOpacity style={styles.deleteBtn} onPress={handleDelete}><Text style={styles.deleteText}>Poz</Text></TouchableOpacity>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container:{flex:1,backgroundColor:COLORS.backgroundSecondary,paddingTop:40},
center:{flex:1,justifyContent:'center',alignItems:'center'},
backBtn:{alignSelf:'flex-end',marginRight:24,marginBottom:16},
title:{fontSize:24,fontWeight:'bold',color:COLORS.textPrimary,marginBottom:24},
detailCard:{backgroundColor:COLORS.white,borderRadius:12,padding:20,marginBottom:32},
detailRow:{flexDirection:'row',justifyContent:'space-between',paddingVertical:12},
detailRowBorder:{borderBottomWidth:1,borderBottomColor:COLORS.border},
detailKey:{fontWeight:'600',color:COLORS.textSecondary},
detailValue:{color:COLORS.textPrimary},
sectionTitle:{fontWeight:'700',fontSize:18,marginTop:24,marginBottom:12,color:COLORS.textPrimary},
imageGrid:{flexDirection:'row',flexWrap:'wrap',justifyContent:'space-between',marginTop:12},
imageWrapper:{width:'48%',marginBottom:16},
img:{width:'100%',aspectRatio:4/3,borderRadius:8,backgroundColor:'#eee'},
imageLabel:{marginTop:4,fontSize:12,color:COLORS.textSecondary,textAlign:'center'},
deleteBtn:{backgroundColor:COLORS.error,paddingVertical:16,borderRadius:8,alignItems:'center',marginTop:16},
deleteText:{color:COLORS.white,fontSize:16,fontWeight:'600'},
});
export default CardOrderDetailsScreen;

View File

@@ -0,0 +1,84 @@
import React, { useCallback, useState } from 'react';
import { View, Text, StyleSheet, FlatList, TouchableOpacity, ActivityIndicator, RefreshControl, SafeAreaView } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { useNavigation, useFocusEffect } from '@react-navigation/native';
import { StatusBar } from 'expo-status-bar';
import apiService from '../../services/apiService';
import { COLORS } from '../../constants/colors';
import { useBaseEnums } from '../../contexts/BaseEnumsContext';
const CARD_BG = '#EFF6FF';
const CIRCLE_BG = '#7FB3FF';
const CardOrdersScreen = () => {
const navigation = useNavigation();
const [orders,setOrders] = useState([]);
const [loading,setLoading]=useState(true);
const [refreshing,setRefreshing]=useState(false);
const { getLabel } = useBaseEnums();
const fetchData = async()=>{
try{
const res = await apiService.getCardOrders();
if(res.success) setOrders(Array.isArray(res.data)?res.data:[]);
else console.warn(res.error);
}catch(e){ console.warn(e.message);} finally {setLoading(false); setRefreshing(false);} };
useFocusEffect(useCallback(()=>{ fetchData(); },[]));
const onRefresh = ()=>{ setRefreshing(true); fetchData(); };
const handlePress = (item)=>{ navigation.navigate('CardOrderDetails',{orderId:item.id}); };
const renderItem=({item})=>{
const created = item.created_at? new Date(item.created_at).toLocaleDateString():'';
return (
<TouchableOpacity style={styles.card} onPress={()=>handlePress(item)}>
<View style={styles.circle}><Text style={styles.circleText}>{item.id}</Text></View>
<View style={styles.content}>
<Text style={styles.titleText}>{item.unique_id}</Text>
<Text style={styles.line}>Kart görnüşi: {getLabel('card_types',item.card_type_id)}</Text>
<Text style={styles.line}>Tölenildi: {item.paid? 'Hawa':'Ýok'}</Text>
<Text style={styles.status}>{item.status}</Text>
<Text style={styles.date}>{created}</Text>
</View>
</TouchableOpacity>
); };
if(loading) return <View style={styles.center}><ActivityIndicator size="large" color={COLORS.primary}/></View>;
return (
<SafeAreaView style={styles.container}>
<StatusBar style="dark" />
<View style={styles.header}>
<TouchableOpacity onPress={()=>navigation.goBack()} style={{paddingRight:12}}>
<Ionicons name="arrow-back" size={24} color={COLORS.textPrimary}/>
</TouchableOpacity>
<Text style={styles.headerTitle}>Täze kart sargytlary</Text>
</View>
<FlatList data={orders} keyExtractor={i=>i.id.toString()} renderItem={renderItem} refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh}/>} contentContainerStyle={orders.length===0 && styles.empty} ListEmptyComponent={<Text style={styles.emptyText}>Sargyt ýok</Text>} />
<TouchableOpacity style={styles.fab} onPress={()=>navigation.navigate('CreateCardOrder')}>
<Ionicons name="add" size={28} color={COLORS.white}/>
</TouchableOpacity>
</SafeAreaView>
); };
const styles = StyleSheet.create({
container:{flex:1,backgroundColor:COLORS.backgroundSecondary},
header:{flexDirection:'row',alignItems:'center',paddingHorizontal:24,paddingVertical:16},
headerTitle:{fontSize:20,fontWeight:'bold',color:COLORS.textPrimary},
center:{flex:1,justifyContent:'center',alignItems:'center'},
card:{flexDirection:'row',backgroundColor:CARD_BG,marginHorizontal:24,marginTop:16,borderRadius:12,padding:16},
circle:{width:40,height:40,borderRadius:20,backgroundColor:CIRCLE_BG,alignItems:'center',justifyContent:'center',marginRight:16},
circleText:{color:COLORS.white,fontWeight:'600'},
content:{flex:1},
titleText:{fontWeight:'700',color:COLORS.textPrimary},
line:{fontSize:12,color:COLORS.textSecondary},
status:{fontSize:13,fontWeight:'600',marginTop:2},
date:{fontSize:11,color:COLORS.textSecondary},
fab:{position:'absolute',bottom:32,right:32,width:56,height:56,borderRadius:28,backgroundColor:COLORS.primary,alignItems:'center',justifyContent:'center',elevation:4},
empty:{flex:1,justifyContent:'center',alignItems:'center'},
emptyText:{color:COLORS.textSecondary,fontSize:16},
});
export default CardOrdersScreen;

View File

@@ -0,0 +1,201 @@
import React, { useState, useEffect, useRef } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, SafeAreaView, ScrollView, Modal } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { useNavigation } from '@react-navigation/native';
import { StatusBar } from 'expo-status-bar';
import apiService from '../../services/apiService';
import { COLORS } from '../../constants/colors';
import Input from '../../components/Input';
import SelectInput from '../../components/SelectInput';
import DateInput from '../../components/DateInput';
import ImageInput from '../../components/ImageInput';
import { useAuth } from '../../contexts/AuthContext';
import { useBaseEnums } from '../../contexts/BaseEnumsContext';
import { WebView } from 'react-native-webview';
const PASSPORT_SERIES = ['I-AS','I-MR','II-MR','I-AH','II-AH','I-LB','II-LB','I-BN','II-BN','I-DZ','II-DZ'];
const CreateCardOrderScreen = ()=>{
const navigation=useNavigation();
const { user } = useAuth();
const { getOptions, getBranches } = useBaseEnums();
const [cardState,setCardState]=useState('');
const [cardType,setCardType]=useState('');
const [region,setRegion]=useState('');
const [branchId,setBranchId]=useState('');
const [customerName,setCustomerName]=useState('');
const [customerSurname,setCustomerSurname]=useState('');
const [customerPatro,setCustomerPatro]=useState('');
const [oldSurname,setOldSurname]=useState('');
const [bornAt,setBornAt]=useState('');
const [phone,setPhone]=useState('');
const [phoneAdditional,setPhoneAdditional]=useState('');
const [citizenship] = useState('');
const [passportSerie,setPassportSerie]=useState('');
const [passportId,setPassportId]=useState('');
const [passportGivenAt,setPassportGivenAt]=useState('');
const [passportGivenBy,setPassportGivenBy]=useState('');
const [bornPlace,setBornPlace]=useState('');
const [passportAddress,setPassportAddress]=useState('');
const [realAddress,setRealAddress]=useState('');
const [jobLocation,setJobLocation]=useState('');
const [passportOne,setPassportOne]=useState(null);
const [passportTwo,setPassportTwo]=useState(null);
const [passportThree,setPassportThree]=useState(null);
const [passportFour,setPassportFour]=useState(null);
const [branchOptions,setBranchOptions]=useState([]);
const [loading,setLoading]=useState(false);
const [webVisible,setWebVisible]=useState(false);
const [paymentUrl,setPaymentUrl]=useState('');
const [newOrderId,setNewOrderId]=useState(null);
const cardStateOptions = getOptions('card_states');
const cardTypeOptions = getOptions('card_types');
const regionOptions = getOptions('regions');
// Prefill from user
useEffect(()=>{
if(user){
if(user.passport_serie) setPassportSerie(user.passport_serie);
if(user.passport_id) setPassportId(String(user.passport_id));
if(user.region) setRegion(user.region);
if(user.phone) setPhone(String(user.phone).slice(-8));
if(user.name){ const parts=user.name.split(' '); setCustomerName(parts[0]); setCustomerSurname(parts[1]||''); }
}
},[user]);
useEffect(()=>{ (async()=>{ if(region){ const br=await getBranches(region); setBranchOptions(br.map(r=>({label:r.name,value:r.id}))); }else setBranchOptions([]); })(); },[region]);
const validate=()=>{
return cardState && cardType && region && branchId && customerName && customerSurname && bornAt && passportSerie && passportId.trim() && passportGivenAt && passportGivenBy && bornPlace && jobLocation && passportAddress && realAddress && phone && passportOne && passportTwo && passportThree && passportFour;
};
const handleSubmit=async()=>{
if(!validate()){ Alert.alert('Ýalňyşlyk','Ähli zerur meýdançalar doldurylmaly'); return; }
const fd=new FormData();
fd.append('card_state_id',cardState);
fd.append('card_type_id',cardType);
fd.append('region',region);
fd.append('branch_id',branchId);
fd.append('customer_name',customerName);
fd.append('customer_surname',customerSurname);
if(customerPatro) fd.append('customer_patronic_name',customerPatro);
if(oldSurname) fd.append('old_surname',oldSurname);
fd.append('born_at',bornAt);
fd.append('passport_serie',passportSerie);
fd.append('passport_id',passportId.trim());
fd.append('passport_given_at',passportGivenAt);
fd.append('passport_given_by',passportGivenBy);
fd.append('born_place',bornPlace);
fd.append('job_location',jobLocation);
fd.append('passport_address',passportAddress);
fd.append('real_address',realAddress);
fd.append('phone',parseInt(phone));
if(phoneAdditional) fd.append('phone_additional',parseInt(phoneAdditional));
fd.append('passport_one',{uri:passportOne,name:'p1.jpg',type:'image/jpeg'});
fd.append('passport_two',{uri:passportTwo,name:'p2.jpg',type:'image/jpeg'});
fd.append('passport_three',{uri:passportThree,name:'p3.jpg',type:'image/jpeg'});
fd.append('passport_four',{uri:passportFour,name:'p4.jpg',type:'image/jpeg'});
setLoading(true);
const res = await apiService.createCardOrder(fd);
setLoading(false);
if(res.success){
const payment=res.data.payment;
Alert.alert('Üstünlik',res.data.message||'Döredildi');
// Try to get latest order id
const list = await apiService.getCardOrders();
if(list.success && Array.isArray(list.data) && list.data.length>0){ const maxOrder=list.data.reduce((a,b)=>a.id>b.id?a:b); setNewOrderId(maxOrder.id); }
if(payment && payment.url){ setPaymentUrl(payment.url); setWebVisible(true);} else if(newOrderId){ navigation.replace('CardOrderDetails',{orderId:newOrderId}); }
} else {
Alert.alert('Ýalňyşlyk',res.error||'Ýalňyşlyk');
}
};
const webRef=useRef();
const handleWebNav=state=>{
if(state.url.includes('/online-payment-store')){
setTimeout(()=>{ setWebVisible(false); if(newOrderId) navigation.replace('CardOrderDetails',{orderId:newOrderId}); },3000);
}
};
return (
<SafeAreaView style={styles.container}>
<StatusBar style="dark" />
<ScrollView contentContainerStyle={{paddingHorizontal:24,paddingTop:40,paddingBottom:120}} showsVerticalScrollIndicator={false}>
<TouchableOpacity style={styles.backBtn} onPress={()=>navigation.goBack()}><Ionicons name="arrow-back" size={24} color={COLORS.textPrimary}/></TouchableOpacity>
<Text style={styles.title}>Täze kart sargyt</Text>
{/* Card */}
<SelectInput label="*Kart çykarylmagynyň sebabi" value={cardState} onValueChange={setCardState} options={cardStateOptions} placeholder="Saýla" />
<SelectInput label="*Kart görnüşi" value={cardType} onValueChange={setCardType} options={cardTypeOptions} placeholder="Saýla" />
{/* Location */}
<Text style={styles.sectionTitle}>Lokasiýa</Text>
<SelectInput label="*Welaýat" value={region} onValueChange={setRegion} options={regionOptions} placeholder="Saýla" />
<SelectInput label="*Şahamça" value={branchId} onValueChange={setBranchId} options={branchOptions} placeholder="Saýla" />
{/* Personal */}
<Text style={styles.sectionTitle}>Şahsy maglumatlar</Text>
<Input label="*Ady" value={customerName} onChangeText={setCustomerName}/>
<Input label="*Familiýasy" value={customerSurname} onChangeText={setCustomerSurname}/>
<Input label="Atasynyň ady" value={customerPatro} onChangeText={setCustomerPatro}/>
<Input label="Köne familiýa" value={oldSurname} onChangeText={setOldSurname}/>
<DateInput label="*Doglan güni" value={bornAt} onChange={setBornAt}/>
<Input label="*Telefon" value={phone} onChangeText={setPhone} keyboardType="numeric" maxLength={8}/>
<Input label="Telefon goşmaça" value={phoneAdditional} onChangeText={setPhoneAdditional} keyboardType="numeric" maxLength={8}/>
<Input label="*Ýazgy edilen salgyňyz" value={passportAddress} onChangeText={setPassportAddress}/>
<Input label="*Häzirki ýaşaýyş ýeri" value={realAddress} onChangeText={setRealAddress}/>
<Input label="*Işleýän ýeriňiz we wezipäňiz" value={jobLocation} onChangeText={setJobLocation}/>
{/* Passport */}
<Text style={styles.sectionTitle}>Pasport</Text>
<SelectInput label="*Passport seriýasy" value={passportSerie} onValueChange={setPassportSerie} options={PASSPORT_SERIES.map(v=>({label:v,value:v}))} placeholder="Saýla" />
<Input label="*Passport belgisi" value={passportId} onChangeText={setPassportId} keyboardType="numeric" />
<DateInput label="*Passport berlen senesi" value={passportGivenAt} onChange={setPassportGivenAt}/>
<Input label="*Kim tarapyndan berildi" value={passportGivenBy} onChangeText={setPassportGivenBy}/>
<Input label="*Doglan ýeri (passport)" value={bornPlace} onChangeText={setBornPlace}/>
{/* Images */}
<Text style={styles.sectionTitle}>Pasport faýllar</Text>
<View style={{flexDirection:'row',flexWrap:'wrap',justifyContent:'space-between'}}>
<ImageInput label="Pasport (sahypa 1) *" image={passportOne} onChange={setPassportOne}/>
<ImageInput label="Pasport (2-3 sah.) *" image={passportTwo} onChange={setPassportTwo}/>
<ImageInput label="Pasport (8-9 sah.) *" image={passportThree} onChange={setPassportThree}/>
<ImageInput label="Pasport (32 sah.) *" image={passportFour} onChange={setPassportFour}/>
</View>
<TouchableOpacity style={styles.submitBtn} onPress={handleSubmit} disabled={loading}>
{loading?<ActivityIndicator color={COLORS.white}/> : <Text style={styles.submitText}>Sargyt et</Text>}
</TouchableOpacity>
</ScrollView>
{/* Payment WebView */}
<Modal visible={webVisible} onRequestClose={()=>setWebVisible(false)} animationType="slide">
<SafeAreaView style={{flex:1}}>
<View style={{flexDirection:'row',alignItems:'center',padding:12,backgroundColor:COLORS.primary}}>
<TouchableOpacity onPress={()=>{setWebVisible(false); if(newOrderId) navigation.replace('CardOrderDetails',{orderId:newOrderId});}}><Ionicons name="close" size={24} color="#fff"/></TouchableOpacity>
<Text style={{color:'#fff',fontWeight:'600',marginLeft:16}}>Töleg</Text>
</View>
{paymentUrl!=='' && <WebView ref={webRef} source={{uri:paymentUrl}} onNavigationStateChange={handleWebNav}/>}
</SafeAreaView>
</Modal>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container:{flex:1,backgroundColor:COLORS.backgroundSecondary},
backBtn:{marginBottom:24},
title:{fontSize:24,fontWeight:'bold',color:COLORS.textPrimary,marginBottom:24},
sectionTitle:{fontWeight:'700',fontSize:18,marginTop:24,marginBottom:12,color:COLORS.textPrimary},
submitBtn:{marginTop:32,backgroundColor:COLORS.primary,paddingVertical:16,borderRadius:8,alignItems:'center'},
submitText:{color:COLORS.white,fontSize:16,fontWeight:'600'},
});
export default CreateCardOrderScreen;

View File

@@ -59,20 +59,20 @@ const LoanOrderDetailsScreen = () => {
}, []);
const handleDelete = () => {
Alert.alert('Confirm', 'Delete this order?', [
{ text: 'Cancel', style: 'cancel' },
{ text: 'Delete', style: 'destructive', onPress: deleteOrder },
Alert.alert('Tassykla', 'Bu sargydy pozmak isleýärsiňizmi?', [
{ text: 'Goýbolsun', style: 'cancel' },
{ text: 'Poz', style: 'destructive', onPress: deleteOrder },
]);
};
const deleteOrder = async () => {
const res = await apiService.deleteLoanOrder(orderId);
if (res.success) {
Alert.alert('Deleted', res.message || 'Order deleted', [
{ text: 'OK', onPress: () => navigation.goBack() },
Alert.alert('Pozuldy', res.message || 'Sargyt pozuldy', [
{ text: 'Bolýar', onPress: () => navigation.goBack() },
]);
} else {
Alert.alert('Error', res.error || 'Could not delete');
Alert.alert('Ýalňyşlyk', res.error || 'Pozup bolmady');
}
};

View File

@@ -34,13 +34,13 @@ const MenuScreen = () => {
{ id: 8, title: 'Kart pin bukjalar', icon: 'key', description: 'Pin bukjalar' },
],
},
{
title: 'Halkara tölegler',
items: [
{ id: 9, title: 'Visa/Master tölegleri (talyplar üçin)', icon: 'logo-usd', description: 'Visa/Master' },
{ id: 10, title: 'Sber tölegler (talyplar üçin)', icon: 'globe', description: 'Sber tölegler' },
],
},
// {
// title: 'Halkara tölegler',
// items: [
// { id: 9, title: 'Visa/Master tölegleri (talyplar üçin)', icon: 'logo-usd', description: 'Visa/Master' },
// { id: 10, title: 'Sber tölegler (talyplar üçin)', icon: 'globe', description: 'Sber tölegler' },
// ],
// },
];
const handleMenuItemPress = (item) => {
@@ -50,6 +50,8 @@ const MenuScreen = () => {
navigation.navigate('LoanRemainingOrders');
} else if (item.id === 3) {
navigation.navigate('LoanPaidOffLetterOrders');
} else if (item.id === 4) {
navigation.navigate('CardOrders');
} else if (item.id === 5) {
navigation.navigate('CardTransactionOrders');
} else if (item.id === 6) {

View File

@@ -103,7 +103,7 @@ const ProfileScreen = () => {
handleSecuritySettings();
break;
default:
Alert.alert('Üns beriň', 'Bu funksiýa entek işlenok');
// Alert.alert('Üns beriň', 'Bu funksiýa entek işlenok');
}
};

View File

@@ -524,6 +524,46 @@ class ApiService {
return { success: false, error: error.message };
}
}
// ================================
// Card Orders (Täze kart)
// ================================
async getCardOrders() {
try {
const data = await authService.getCardOrders();
return { success: true, data };
} catch (error) {
return { success: false, error: error.message };
}
}
async createCardOrder(payload) {
try {
const response = await authService.createCardOrder(payload);
return { success: true, data: response };
} catch (error) {
return { success: false, error: error.message };
}
}
async getCardOrder(orderId) {
try {
const data = await authService.getCardOrder(orderId);
return { success: true, data };
} catch (error) {
return { success: false, error: error.message };
}
}
async deleteCardOrder(orderId) {
try {
const response = await authService.deleteCardOrder(orderId);
return { success: true, message: response.message };
} catch (error) {
return { success: false, error: error.message };
}
}
}
export default new ApiService();

View File

@@ -563,6 +563,30 @@ class AuthService {
async deleteCardPinOrder(orderId) {
return this.makeRequest(`/card-pin-order/${orderId}`, null, true, 'DELETE');
}
// ================================
// Card Orders (Täze kart)
// ================================
// LIST
async getCardOrders() {
return this.makeRequest('/card-order', null, true, 'GET');
}
// CREATE returns { message, payment { status, url } }
async createCardOrder(payload) {
return this.makeRequest('/card-order', payload, true, 'POST');
}
// SHOW
async getCardOrder(orderId) {
return this.makeRequest(`/card-order/${orderId}`, null, true, 'GET');
}
// DELETE
async deleteCardOrder(orderId) {
return this.makeRequest(`/card-order/${orderId}`, null, true, 'DELETE');
}
}
export default new AuthService();