diff --git a/src/navigation/MenuNavigator.js b/src/navigation/MenuNavigator.js
index 2b9c3f9..238f968 100644
--- a/src/navigation/MenuNavigator.js
+++ b/src/navigation/MenuNavigator.js
@@ -18,6 +18,9 @@ import CardBalanceOrderDetailsScreen from '../screens/Card/CardBalanceOrderDetai
import CardRequisiteOrdersScreen from '../screens/Card/CardRequisiteOrdersScreen';
import CreateCardRequisiteOrderScreen from '../screens/Card/CreateCardRequisiteOrderScreen';
import CardRequisiteOrderDetailsScreen from '../screens/Card/CardRequisiteOrderDetailsScreen';
+import CardPinOrdersScreen from '../screens/Card/CardPinOrdersScreen';
+import CreateCardPinOrderScreen from '../screens/Card/CreateCardPinOrderScreen';
+import CardPinOrderDetailsScreen from '../screens/Card/CardPinOrderDetailsScreen';
const Stack = createStackNavigator();
@@ -41,6 +44,9 @@ const MenuNavigator = () => (
+
+
+
);
diff --git a/src/screens/Card/CardPinOrderDetailsScreen.js b/src/screens/Card/CardPinOrderDetailsScreen.js
new file mode 100644
index 0000000..15a10e6
--- /dev/null
+++ b/src/screens/Card/CardPinOrderDetailsScreen.js
@@ -0,0 +1,145 @@
+import React, { useEffect, useState } from 'react';
+import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, ScrollView, SafeAreaView, Image, 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 }) => (
+
+ {label}
+ {String(value)}
+
+);
+
+const CardPinOrderDetailsScreen = () => {
+ 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 fetchDetails=async()=>{
+ setLoading(true);
+ const res = await apiService.getCardPinOrder(orderId);
+ if(res.success){
+ setOrder(res.data);
+ if(res.data?.region && res.data?.branch_id){
+ (async()=>{ 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 || 'Ýalňyşlyk'); }
+ setLoading(false);
+ };
+ useEffect(()=>{fetchDetails();},[]);
+
+ const handleDelete=()=>{ Alert.alert('Tassykla','Pozmakçy my?',[{text:'Goýbolsun',style:'cancel'},{text:'Poz',style:'destructive',onPress:async()=>{
+ const r=await apiService.deleteCardPinOrder(orderId);
+ if(r.success){ Alert.alert('Pozuldy', r.message || 'Pozuldy', [{text:'OK',onPress:()=>navigation.goBack()}]); } else { Alert.alert('Ýalňyşlyk', r.error || 'Ýalňyşlyk'); }
+ }}]); };
+
+ if(loading){ return ; }
+ if(!order){ return Maglumat ýok; }
+
+ return (
+
+
+ navigation.goBack()}>
+
+
+
+
+ Kart pin bukjalar
+
+ {/* Card info */}
+
+
+ {order.card_type_id && }
+ {order.card_number && }
+
+
+ {/* Location */}
+ {(order.region || branchName) && (
+ <>
+ Lokasiýa
+
+ {order.region && }
+ {(branchName || order.branch_id) && }
+
+ >
+ )}
+
+ {/* Personal */}
+ Şahsy maglumatlar
+
+ {order.customer_name && }
+ {order.customer_surname && }
+ {order.customer_patronic_name && }
+ {order.born_at && }
+ {order.phone && }
+
+
+ {/* Passport */}
+ Pasport
+
+ {(order.passport_serie || order.passport_id) && }
+
+ {order.passport_one && (
+ Linking.openURL(order.passport_one)} style={styles.imageWrapper}>
+
+ 1-nji sah.
+
+ )}
+ {order.passport_two && (
+ Linking.openURL(order.passport_two)} style={styles.imageWrapper}>
+
+ 2-3 sah.
+
+ )}
+ {order.passport_three && (
+ Linking.openURL(order.passport_three)} style={styles.imageWrapper}>
+
+ 8-9 sah.
+
+ )}
+ {order.passport_four && (
+ Linking.openURL(order.passport_four)} style={styles.imageWrapper}>
+
+ 32 sah.
+
+ )}
+
+
+
+
+ Poz
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container:{flex:1,backgroundColor:COLORS.backgroundSecondary,paddingTop:40},
+ centered:{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},
+ passportImage:{width:'100%',aspectRatio:4/3,borderRadius:8,backgroundColor:'#f0f0f0'},
+ 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 CardPinOrderDetailsScreen;
\ No newline at end of file
diff --git a/src/screens/Card/CardPinOrdersScreen.js b/src/screens/Card/CardPinOrdersScreen.js
new file mode 100644
index 0000000..eb7e353
--- /dev/null
+++ b/src/screens/Card/CardPinOrdersScreen.js
@@ -0,0 +1,123 @@
+import React, { useState, useCallback } from 'react';
+import { View, Text, StyleSheet, FlatList, ActivityIndicator, TouchableOpacity, 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 = '#F1F9F1';
+const CIRCLE_BG = '#A2E4A4';
+
+const CardPinOrdersScreen = () => {
+ const navigation = useNavigation();
+ const [orders, setOrders] = useState([]);
+ const { getLabel } = useBaseEnums();
+ const [loading, setLoading] = useState(true);
+ const [refreshing, setRefreshing] = useState(false);
+
+ const fetchOrders = async () => {
+ try {
+ const res = await apiService.getCardPinOrders();
+ 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(() => {
+ fetchOrders();
+ }, [])
+ );
+
+ const onRefresh = () => {
+ setRefreshing(true);
+ fetchOrders();
+ };
+
+ const handleItemPress = (item) => {
+ navigation.navigate('CardPinOrderDetails', { orderId: item.id });
+ };
+
+ const renderItem = ({ item }) => {
+ const masked = item.card_number ? `${item.card_number.slice(0,6)}******${item.card_number.slice(-4)}` : '';
+ const created = item.created_at ? new Date(item.created_at).toLocaleDateString() : '';
+ const typeLabel = getLabel('card_types', item.card_type_id);
+ const passportLine = item.passport_serie && item.passport_id ? `${item.passport_serie} ${item.passport_id}` : '';
+
+ return (
+ handleItemPress(item)}>
+
+ {item.id}
+
+
+ {masked}
+ {typeLabel && {typeLabel}}
+ {passportLine !== '' && {passportLine}}
+ {created}
+
+
+ );
+ };
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ navigation.goBack()} style={{ paddingRight: 12 }}>
+
+
+ Kart pin bukjalar
+
+ item.id?.toString()}
+ renderItem={renderItem}
+ contentContainerStyle={orders.length === 0 && styles.emptyContainer}
+ refreshControl={}
+ ListEmptyComponent={Heniz sargyt ýok}
+ />
+
+ navigation.navigate('CreateCardPinOrder')}>
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: { flex: 1, backgroundColor: COLORS.backgroundSecondary },
+ centered: { flex: 1, alignItems: 'center', justifyContent: 'center' },
+ header: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 24, paddingVertical: 16 },
+ headerTitle: { fontSize: 20, fontWeight: 'bold', color: COLORS.textPrimary },
+ card: { flexDirection: 'row', backgroundColor: CARD_BG, marginHorizontal: 24, marginTop: 16, borderRadius: 12, padding: 16, alignItems: 'center' },
+ circle: { width: 40, height: 40, borderRadius: 20, backgroundColor: CIRCLE_BG, alignItems: 'center', justifyContent: 'center', marginRight: 16 },
+ circleText: { color: COLORS.white, fontWeight: '600' },
+ cardContent: { flex: 1 },
+ cardNumber: { fontWeight: '700', color: COLORS.textPrimary, marginBottom: 4 },
+ dateText: { color: COLORS.textSecondary, fontSize: 12 },
+ emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center' },
+ emptyText: { fontSize: 16, color: COLORS.textSecondary },
+ fab: { position: 'absolute', bottom: 32, right: 32, backgroundColor: COLORS.primary, width: 56, height: 56, borderRadius: 28, alignItems: 'center', justifyContent: 'center', elevation: 4 },
+ typeText:{color:COLORS.textSecondary,fontSize:14},
+ passportText:{color:COLORS.textSecondary,fontSize:12},
+});
+
+export default CardPinOrdersScreen;
\ No newline at end of file
diff --git a/src/screens/Card/CreateCardPinOrderScreen.js b/src/screens/Card/CreateCardPinOrderScreen.js
new file mode 100644
index 0000000..3d2a0c5
--- /dev/null
+++ b/src/screens/Card/CreateCardPinOrderScreen.js
@@ -0,0 +1,149 @@
+import React, { useState, useEffect } from 'react';
+import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, SafeAreaView, ScrollView } 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 { useAuth } from '../../contexts/AuthContext';
+import DateInput from '../../components/DateInput';
+import ImageInput from '../../components/ImageInput';
+import { useBaseEnums } from '../../contexts/BaseEnumsContext';
+
+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 formatCardNumber = (val) => {
+ const digits = String(val).replace(/[^0-9]/g,'').slice(0,16);
+ return (digits.match(/.{1,4}/g)||[]).join(' ');
+};
+
+const CreateCardPinOrderScreen = () => {
+ const navigation = useNavigation();
+ const { user } = useAuth();
+ const { getOptions, getBranches } = useBaseEnums();
+
+ const [passportSerie,setPassportSerie]=useState('');
+ const [passportId,setPassportId]=useState('');
+ const [cardNumber,setCardNumber]=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 [bornAt,setBornAt]=useState('');
+ const [phone,setPhone]=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 cardTypeOptions = getOptions('card_types');
+ const regionOptions = getOptions('regions');
+
+ useEffect(()=>{
+ if(user){
+ if(user.passport_serie) setPassportSerie(user.passport_serie);
+ if(user.passport_id) setPassportId(String(user.passport_id));
+ if(user.card_number) setCardNumber(formatCardNumber(user.card_number));
+ 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 b = await getBranches(region); setBranchOptions(b.map(br=>({label:br.name,value:br.id})));} else {setBranchOptions([]);} })();},[region]);
+
+ const handleCardNumberChange = (v)=> setCardNumber(formatCardNumber(v));
+
+ const handleSubmit = async()=>{
+ if(!cardType||!cardNumber.trim()||!region||!branchId||!customerName||!customerSurname||!bornAt||!phone||!passportSerie||!passportId.trim()||!passportOne||!passportTwo||!passportThree||!passportFour){
+ Alert.alert('Ýalňyşlyk','Ähli zerur meýdançalar doldurylmaly'); return;
+ }
+
+ const payload = new FormData();
+ payload.append('card_type_id', cardType);
+ payload.append('card_number', cardNumber.replace(/\s+/g,''));
+ payload.append('region', region);
+ payload.append('branch_id', branchId);
+ payload.append('customer_name', customerName);
+ payload.append('customer_surname', customerSurname);
+ if(customerPatro) payload.append('customer_patronic_name', customerPatro);
+ payload.append('born_at', bornAt);
+ payload.append('phone', parseInt(phone));
+ payload.append('passport_serie', passportSerie);
+ payload.append('passport_id', passportId.trim());
+ payload.append('passport_one', { uri: passportOne, name: 'p1.jpg', type:'image/jpeg'});
+ payload.append('passport_two', { uri: passportTwo, name: 'p2.jpg', type:'image/jpeg'});
+ payload.append('passport_three', { uri: passportThree, name: 'p3.jpg', type:'image/jpeg'});
+ payload.append('passport_four', { uri: passportFour, name: 'p4.jpg', type:'image/jpeg'});
+
+ setLoading(true);
+ const res = await apiService.createCardPinOrder(payload);
+ setLoading(false);
+ if(res.success){
+ Alert.alert('Üstünlik', res.message || 'Döredildi', [{ text:'OK', onPress:()=>navigation.goBack() }]);
+ } else {
+ Alert.alert('Ýalňyşlyk', res.error || 'Ýalňyşlyk ýüze çykdy');
+ }
+ };
+
+ return (
+
+
+
+ navigation.goBack()}>
+
+
+ Täze sargyt
+
+
+
+
+ {/* Location */}
+ Lokasiýa
+
+ ({label:b.label,value:b.value}))} placeholder="Saýla" />
+
+ {/* Personal */}
+ Şahsy maglumatlar
+
+
+
+
+
+
+ {/* Passport */}
+ Pasport
+ ({label:v,value:v}))} placeholder="Saýla" />
+
+
+
+
+
+
+
+
+
+ {loading ? : Ýatda sakla}
+
+
+
+ );
+};
+
+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 CreateCardPinOrderScreen;
\ No newline at end of file
diff --git a/src/screens/Loan/CreateLoanOrderScreen.js b/src/screens/Loan/CreateLoanOrderScreen.js
index 3edf7eb..3674115 100644
--- a/src/screens/Loan/CreateLoanOrderScreen.js
+++ b/src/screens/Loan/CreateLoanOrderScreen.js
@@ -188,6 +188,11 @@ const CreateLoanOrderScreen = () => {
return;
}
+ if (passportId.length != 6) {
+ Alert.alert('Ýalňyşlyk', 'Passport nomeri 6 san bolmaly');
+ return;
+ }
+
const rawCardNumber = cardNumber.replace(/[^0-9]/g, '');
if (rawCardNumber.length !== 16) {
Alert.alert('Ýalňyşlyk', 'Kart belgisi dogry dolduryň (16 sany rakam)');
@@ -345,7 +350,9 @@ const CreateLoanOrderScreen = () => {
-
+
+
+
@@ -391,7 +398,7 @@ const CreateLoanOrderScreen = () => {
{/* Guarantor passport */}
-
+
{needsSecondGuarantor && (
<>
@@ -407,7 +414,7 @@ const CreateLoanOrderScreen = () => {
-
+
>
)}
diff --git a/src/screens/Main/MenuScreen.js b/src/screens/Main/MenuScreen.js
index 0667d74..eeeb55b 100644
--- a/src/screens/Main/MenuScreen.js
+++ b/src/screens/Main/MenuScreen.js
@@ -56,6 +56,8 @@ const MenuScreen = () => {
navigation.navigate('CardRequisiteOrders');
} else if (item.id === 7) {
navigation.navigate('CardBalanceOrders');
+ } else if (item.id === 8) {
+ navigation.navigate('CardPinOrders');
} else {
console.log('Menu item pressed:', item.title);
}
diff --git a/src/services/apiService.js b/src/services/apiService.js
index 6dd18a8..b119a4e 100644
--- a/src/services/apiService.js
+++ b/src/services/apiService.js
@@ -484,6 +484,46 @@ class ApiService {
return { success: false, error: error.message };
}
}
+
+ // ================================
+ // Card Pin Orders (Kart pin bukjalar)
+ // ================================
+
+ async getCardPinOrders() {
+ try {
+ const data = await authService.getCardPinOrders();
+ return { success: true, data };
+ } catch (error) {
+ return { success: false, error: error.message };
+ }
+ }
+
+ async createCardPinOrder(payload) {
+ try {
+ const response = await authService.createCardPinOrder(payload);
+ return { success: true, message: response.message };
+ } catch (error) {
+ return { success: false, error: error.message };
+ }
+ }
+
+ async getCardPinOrder(orderId) {
+ try {
+ const data = await authService.getCardPinOrder(orderId);
+ return { success: true, data };
+ } catch (error) {
+ return { success: false, error: error.message };
+ }
+ }
+
+ async deleteCardPinOrder(orderId) {
+ try {
+ const response = await authService.deleteCardPinOrder(orderId);
+ return { success: true, message: response.message };
+ } catch (error) {
+ return { success: false, error: error.message };
+ }
+ }
}
export default new ApiService();
\ No newline at end of file
diff --git a/src/services/authService.js b/src/services/authService.js
index 191e07d..2dcd04f 100644
--- a/src/services/authService.js
+++ b/src/services/authService.js
@@ -534,6 +534,35 @@ class AuthService {
async downloadCardRequisites(orderId) {
return this.makeRequest(`/card-requisites-download/${orderId}`, null, true, 'GET');
}
+
+ // ================================
+ // Card Pin Orders (Kart pin bukjalar)
+ // ================================
+
+ // LIST
+ async getCardPinOrders() {
+ return this.makeRequest('/card-pin-order', null, true, 'GET');
+ }
+
+ // CREATE (multipart payload similar to Card Requisite but without month/year)
+ async createCardPinOrder(payload) {
+ return this.makeRequest('/card-pin-order', payload, true, 'POST');
+ }
+
+ // SHOW
+ async getCardPinOrder(orderId) {
+ return this.makeRequest(`/card-pin-order/${orderId}`, null, true, 'GET');
+ }
+
+ // UPDATE
+ async updateCardPinOrder(orderId, payload) {
+ return this.makeRequest(`/card-pin-order/${orderId}`, payload, true, 'POST');
+ }
+
+ // DELETE
+ async deleteCardPinOrder(orderId) {
+ return this.makeRequest(`/card-pin-order/${orderId}`, null, true, 'DELETE');
+ }
}
export default new AuthService();
\ No newline at end of file