diff --git a/src/contexts/BaseEnumsContext.js b/src/contexts/BaseEnumsContext.js index a064860..754959a 100644 --- a/src/contexts/BaseEnumsContext.js +++ b/src/contexts/BaseEnumsContext.js @@ -6,6 +6,8 @@ const BaseEnumsContext = createContext({ enums: null, refresh: () => {}, getEnum export const BaseEnumsProvider = ({ children }) => { const [enums, setEnums] = useState(null); const [lastFetched, setLastFetched] = useState(0); + const [branchesByRegion, setBranchesByRegion] = useState({}); + const [branchesFetchedAt, setBranchesFetchedAt] = useState(0); const fetchEnums = async () => { try { @@ -18,11 +20,24 @@ export const BaseEnumsProvider = ({ children }) => { } }; + const fetchBranches = async () => { + try { + const res = await fetch(`${API_CONFIG.BASE_URL}/branches?groupBy=region`); + const json = await res.json(); + setBranchesByRegion(json || {}); + setBranchesFetchedAt(Date.now()); + } catch (e) { + console.warn('Failed to fetch branches', e.message); + } + }; + // initial fetch and 60s refresh useEffect(() => { fetchEnums(); + fetchBranches(); const id = setInterval(fetchEnums, 60000); - return () => clearInterval(id); + const idB = setInterval(fetchBranches, 60000); + return () => { clearInterval(id); clearInterval(idB);} ; }, []); const getEnums = async () => { @@ -42,8 +57,15 @@ export const BaseEnumsProvider = ({ children }) => { return Object.entries(enums[category]).map(([value, label]) => ({ value, label })); }; + const getBranches = async (regionKey) => { + if (Date.now() - branchesFetchedAt > 60000) { + await fetchBranches(); + } + return regionKey ? branchesByRegion[regionKey] || [] : []; + }; + return ( - + {children} ); diff --git a/src/navigation/MenuNavigator.js b/src/navigation/MenuNavigator.js index 088bbe5..2b9c3f9 100644 --- a/src/navigation/MenuNavigator.js +++ b/src/navigation/MenuNavigator.js @@ -15,6 +15,9 @@ import CardTransactionOrderDetailsScreen from '../screens/Card/CardTransactionOr import CardBalanceOrdersScreen from '../screens/Card/CardBalanceOrdersScreen'; import CreateCardBalanceOrderScreen from '../screens/Card/CreateCardBalanceOrderScreen'; import CardBalanceOrderDetailsScreen from '../screens/Card/CardBalanceOrderDetailsScreen'; +import CardRequisiteOrdersScreen from '../screens/Card/CardRequisiteOrdersScreen'; +import CreateCardRequisiteOrderScreen from '../screens/Card/CreateCardRequisiteOrderScreen'; +import CardRequisiteOrderDetailsScreen from '../screens/Card/CardRequisiteOrderDetailsScreen'; const Stack = createStackNavigator(); @@ -35,6 +38,9 @@ const MenuNavigator = () => ( + + + ); diff --git a/src/screens/Auth/.VerificationScreen.js.swp b/src/screens/Auth/.VerificationScreen.js.swp deleted file mode 100644 index 0a8a16f..0000000 Binary files a/src/screens/Auth/.VerificationScreen.js.swp and /dev/null differ diff --git a/src/screens/Card/CardRequisiteOrderDetailsScreen.js b/src/screens/Card/CardRequisiteOrderDetailsScreen.js new file mode 100644 index 0000000..b1493fb --- /dev/null +++ b/src/screens/Card/CardRequisiteOrderDetailsScreen.js @@ -0,0 +1,169 @@ +import React, { useEffect, useState } from 'react'; +import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, ScrollView, SafeAreaView, Image } 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 { Linking } from 'react-native'; +import { useBaseEnums } from '../../contexts/BaseEnumsContext'; + +const DetailRow = ({ label, value, showBorder=true }) => ( + + {label} + {String(value)} + +); + +const CardRequisiteOrderDetailsScreen = () => { + 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 [downloading,setDownloading]=useState(false); + const [branchName,setBranchName]=useState(''); + + const fetchDetails=async()=>{ + setLoading(true); + const res = await apiService.getCardRequisiteOrder(orderId); + if(res.success){ + setOrder(res.data); + // try to resolve branch name if region & branch_id exist + 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){ /* silent */ } + })(); + } + } else { Alert.alert('Error',res.error||'Failed'); } + setLoading(false); + }; + useEffect(()=>{fetchDetails();},[]); + + const handleDownload=async()=>{ + setDownloading(true); + const res = await apiService.downloadCardRequisites(orderId); + setDownloading(false); + if(res.success && res.data?.url){ Linking.openURL(res.data.url); } else { Alert.alert('Ýalňyşlyk', res.error || 'Ýüklenip bilinmedi'); } + }; + + const handleDelete=()=>{ Alert.alert('Tassykla','Pozmakçy my?',[{text:'Goýbolsun',style:'cancel'},{text:'Poz',style:'destructive',onPress:async()=>{ + const r=await apiService.deleteCardRequisiteOrder(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 rekwizitler + + {/* Card info */} + + + {order.card_type_id && } + {order.card_name && } + {order.card_number && } + {order.card_month && order.card_year && } + + + {/* Location */} + {(order.region || branchName) && ( + <> + Lokasiýa + + {order.region && } + {(branchName || order.branch_id) && } + + + )} + + {/* Personal info */} + Ş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) && } + + {/* Image thumbnails */} + + {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. + + )} + + + + {/* Actions */} + + 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}, + actionBtn:{backgroundColor:COLORS.primary,paddingVertical:16,borderRadius:8,alignItems:'center',marginBottom:16}, + actionText:{color:COLORS.white,fontSize:16,fontWeight:'600'}, + deleteBtn:{backgroundColor:COLORS.error,paddingVertical:16,borderRadius:8,alignItems:'center'}, + deleteText:{color:COLORS.white,fontSize:16,fontWeight:'600'}, + imageGrid:{flexDirection:'row',flexWrap:'wrap',justifyContent:'space-between',marginTop:12}, + imageWrapper:{width:'48%',marginBottom:16}, + passportImage:{width:'100%',aspectRatio:4/3,borderRadius:8,backgroundColor:COLORS.gray?COLORS.gray[200]:"#f0f0f0"}, + imageLabel:{marginTop:4,fontSize:12,color:COLORS.textSecondary,textAlign:'center'}, +}); + +export default CardRequisiteOrderDetailsScreen; \ No newline at end of file diff --git a/src/screens/Card/CardRequisiteOrdersScreen.js b/src/screens/Card/CardRequisiteOrdersScreen.js new file mode 100644 index 0000000..c7c1501 --- /dev/null +++ b/src/screens/Card/CardRequisiteOrdersScreen.js @@ -0,0 +1,123 @@ +import React, { useState, useCallback, useRef } 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 CardRequisiteOrdersScreen = () => { + 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.getCardRequisiteOrders(); + 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('CardRequisiteOrderDetails', { orderId: item.id }); + }; + + const renderItem = ({ item }) => { + const masked = item.card_mask_number || (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 rekwizitler + + item.id?.toString()} + renderItem={renderItem} + contentContainerStyle={orders.length === 0 && styles.emptyContainer} + refreshControl={} + ListEmptyComponent={Maglumat ýok} + /> + + navigation.navigate('CreateCardRequisiteOrder')}> + + + + ); +}; + +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 CardRequisiteOrdersScreen; \ No newline at end of file diff --git a/src/screens/Card/CreateCardRequisiteOrderScreen.js b/src/screens/Card/CreateCardRequisiteOrderScreen.js new file mode 100644 index 0000000..195d251 --- /dev/null +++ b/src/screens/Card/CreateCardRequisiteOrderScreen.js @@ -0,0 +1,159 @@ +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 monthOptions = Array.from({ length: 12 }).map((_, i) => ({ label: String(i+1).padStart(2,'0'), value: String(i+1).padStart(2,'0') })); +const yearOptions = Array.from({ length: 60 }).map((_, i) => ({ label: String(new Date().getFullYear()+i), value: String(new Date().getFullYear()+i) })); +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 CreateCardRequisiteOrderScreen = () => { + const navigation = useNavigation(); + const { user } = useAuth(); + const [passportSerie,setPassportSerie]=useState(''); + const [passportId,setPassportId]=useState(''); + const [cardNumber,setCardNumber]=useState(''); + const [cardMonth,setCardMonth]=useState(''); + const [cardYear,setCardYear]=useState(''); + const [cardName,setCardName]=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 [cardType,setCardType]=useState(''); + const [passportOne,setPassportOne]=useState(null); + const [passportTwo,setPassportTwo]=useState(null); + const [passportThree,setPassportThree]=useState(null); + const [passportFour,setPassportFour]=useState(null); + const { getOptions, getBranches } = useBaseEnums(); + const cardTypeOptions = getOptions('card_types'); + const regionOptions = getOptions('regions'); + const [branchOptions,setBranchOptions]=useState([]); + const [loading,setLoading]=useState(false); + + 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.card_month) setCardMonth(String(user.card_month).padStart(2,'0')); + if(user.card_year) setCardYear(String(user.card_year)); + if(user.card_name) setCardName(user.card_name); + 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()||!cardMonth||!cardYear||!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; + } + + setLoading(true); + + const payload=new FormData(); + payload.append('card_type_id',cardType); + payload.append('passport_serie',passportSerie); + payload.append('passport_id',passportId.trim()); + payload.append('card_number',cardNumber.replace(/\s+/g,'')); + payload.append('card_month',cardMonth); + payload.append('card_year',cardYear); + 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_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'}); + if(cardName) payload.append('card_name',cardName); + const res = await apiService.createCardRequisiteOrder(payload); + setLoading(false); + if(res.success){ + Alert.alert('Success',res.message||'Created',[{text:'OK',onPress:()=>navigation.goBack()}]); + }else{ + Alert.alert('Error',res.error||'Failed'); + } + }; + + return ( + + + + navigation.goBack()}> + + + Täze sargyt + + + + + + + {/* Location */} + Lokasiýa + + ({label:b.label,value:b.value}))} placeholder="Saýla" /> + + {/* Personal */} + Şahsy maglumatlar + + + + + + + {/* Passport images */} + 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}, + submitBtn:{marginTop:32,backgroundColor:COLORS.primary,paddingVertical:16,borderRadius:8,alignItems:'center'}, + submitText:{color:COLORS.white,fontSize:16,fontWeight:'600'}, +}); + +export default CreateCardRequisiteOrderScreen; \ No newline at end of file diff --git a/src/screens/Loan/CreateLoanOrderScreen.js b/src/screens/Loan/CreateLoanOrderScreen.js index 862fc22..3edf7eb 100644 --- a/src/screens/Loan/CreateLoanOrderScreen.js +++ b/src/screens/Loan/CreateLoanOrderScreen.js @@ -184,13 +184,13 @@ const CreateLoanOrderScreen = () => { if ( !loanType || !loanAmount || !region || !branchId || !customerName || !customerSurname || !passportSerie || !passportId || !passportGivenAt || !passportGivenBy || !bornAt || !bornPlace || !phone || !phoneHome || !education || !marriageStatus || !passportAddress || !realAddress || !workCompany || !workCompanyAccNum || !workRegion || !workProvinceId || !workPosition || !workSalary || !workStartedAt || !passportOne || !passportTwo || !passportThree || !passportFour || !cardNumber || !cardName || !cardMonth || !cardYear || !guarantorName || !guarantorSurname || !guarantorCardNumber || !guarantorCardName || !guarantorCardMonth || !guarantorCardYear || !guarantorPassportSerie || !guarantorPassportId || (needsSecondGuarantor && ( !guarantor2Name || !guarantor2Surname || !guarantor2CardNumber || !guarantor2CardName || !guarantor2CardMonth || !guarantor2CardYear || !guarantor2PassportSerie || !guarantor2PassportId )) ) { - Alert.alert('Error', 'Fill all required fields'); + Alert.alert('Ýalňyşlyk', 'Ähli zerur meýdançalary dolduryň'); return; } const rawCardNumber = cardNumber.replace(/[^0-9]/g, ''); if (rawCardNumber.length !== 16) { - Alert.alert('Error', 'Kart belgisi dogry dolduryň (16 sany rakam)'); + Alert.alert('Ýalňyşlyk', 'Kart belgisi dogry dolduryň (16 sany rakam)'); return; } @@ -290,9 +290,12 @@ const CreateLoanOrderScreen = () => { }, body: formData, }); + const json = await response.json(); + console.log('[LoanOrder] API status:', response.status, response.ok); console.log('[LoanOrder] API response:', json); + res = { success: response.ok, ...(response.ok ? { message: json.message } : { error: json.message }) }; } catch (e) { console.error('[LoanOrder] API error', e); diff --git a/src/screens/Loan/LoanOrdersScreen.js b/src/screens/Loan/LoanOrdersScreen.js index 5a1735a..cd09a91 100644 --- a/src/screens/Loan/LoanOrdersScreen.js +++ b/src/screens/Loan/LoanOrdersScreen.js @@ -89,7 +89,7 @@ const LoanOrdersScreen = () => { renderItem={renderItem} contentContainerStyle={orders.length === 0 && styles.emptyContainer} refreshControl={} - ListEmptyComponent={No orders yet} + ListEmptyComponent={Maglumat ýok} /> navigation.navigate('CreateLoanOrder')}> diff --git a/src/screens/Main/MenuScreen.js b/src/screens/Main/MenuScreen.js index 3fd4549..0667d74 100644 --- a/src/screens/Main/MenuScreen.js +++ b/src/screens/Main/MenuScreen.js @@ -52,6 +52,8 @@ const MenuScreen = () => { navigation.navigate('LoanPaidOffLetterOrders'); } else if (item.id === 5) { navigation.navigate('CardTransactionOrders'); + } else if (item.id === 6) { + navigation.navigate('CardRequisiteOrders'); } else if (item.id === 7) { navigation.navigate('CardBalanceOrders'); } else { diff --git a/src/screens/Main/ProfileScreen.js b/src/screens/Main/ProfileScreen.js index c91b7c1..e8e68dd 100644 --- a/src/screens/Main/ProfileScreen.js +++ b/src/screens/Main/ProfileScreen.js @@ -59,27 +59,27 @@ const ProfileScreen = () => { { id: 1, title: 'Şahsy maglumatlar', icon: 'person', hasArrow: true }, ], }, - { - title: 'Sazlamalar', - items: [ - { id: 4, title: 'Bildirişler', icon: 'notifications', hasArrow: true }, - { id: 6, title: 'Dil', icon: 'language', value: 'Türkmençe', hasArrow: true }, - { id: 7, title: 'Tema', icon: 'color-palette', value: 'Ýeňil', hasArrow: true }, - ], - }, - { - title: 'Kömek', - items: [ - { id: 8, title: 'Kömek merkezi', icon: 'help-circle', hasArrow: true }, - { id: 9, title: 'Habarlaş', icon: 'chatbubble', hasArrow: true }, - { id: 10, title: 'Baha ber', icon: 'star', hasArrow: true }, - ], - }, + // { + // title: 'Sazlamalar', + // items: [ + // { id: 4, title: 'Bildirişler', icon: 'notifications', hasArrow: true }, + // { id: 6, title: 'Dil', icon: 'language', value: 'Türkmençe', hasArrow: true }, + // { id: 7, title: 'Tema', icon: 'color-palette', value: 'Ýeňil', hasArrow: true }, + // ], + // }, + // { + // title: 'Kömek', + // items: [ + // { id: 8, title: 'Kömek merkezi', icon: 'help-circle', hasArrow: true }, + // { id: 9, title: 'Habarlaş', icon: 'chatbubble', hasArrow: true }, + // { id: 10, title: 'Baha ber', icon: 'star', hasArrow: true }, + // ], + // }, { title: 'Goşmaça', items: [ - { id: 11, title: 'Ulanmak düzgünleri', icon: 'document-text', hasArrow: true }, - { id: 12, title: 'Gizlinlik syýasaty', icon: 'lock-open', hasArrow: true }, + // { id: 11, title: 'Ulanmak düzgünleri', icon: 'document-text', hasArrow: true }, + // { id: 12, title: 'Gizlinlik syýasaty', icon: 'lock-open', hasArrow: true }, { id: 13, title: 'Programma barada', icon: 'information-circle', value: 'v1.0.0', hasArrow: true }, ], }, diff --git a/src/services/apiService.js b/src/services/apiService.js index a93bbd6..6dd18a8 100644 --- a/src/services/apiService.js +++ b/src/services/apiService.js @@ -426,6 +426,64 @@ class ApiService { return { success: false, error: error.message }; } } + + // ================================ + // Card Requisites Orders (Kart rekwizitler) + // ================================ + + async getCardRequisiteOrders() { + try { + const data = await authService.getCardRequisiteOrders(); + return { success: true, data }; + } catch (error) { + return { success: false, error: error.message }; + } + } + + async createCardRequisiteOrder(payload) { + try { + const response = await authService.createCardRequisiteOrder(payload); + return { success: true, message: response.message }; + } catch (error) { + return { success: false, error: error.message }; + } + } + + async getCardRequisiteOrder(orderId) { + try { + const data = await authService.getCardRequisiteOrder(orderId); + return { success: true, data }; + } catch (error) { + return { success: false, error: error.message }; + } + } + + async updateCardRequisiteOrder(orderId, payload) { + try { + const response = await authService.updateCardRequisiteOrder(orderId, payload); + return { success: true, message: response.message }; + } catch (error) { + return { success: false, error: error.message }; + } + } + + async deleteCardRequisiteOrder(orderId) { + try { + const response = await authService.deleteCardRequisiteOrder(orderId); + return { success: true, message: response.message }; + } catch (error) { + return { success: false, error: error.message }; + } + } + + async downloadCardRequisites(orderId) { + try { + const response = await authService.downloadCardRequisites(orderId); + return { success: true, data: response }; + } 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 7a63ea9..191e07d 100644 --- a/src/services/authService.js +++ b/src/services/authService.js @@ -15,9 +15,17 @@ class AuthService { const requestId = Math.random().toString(36).substr(2, 9); try { - const headers = { - ...API_CONFIG.HEADERS, - }; + const headers = { ...API_CONFIG.HEADERS }; + + // Determine body and adjust headers for FormData + let bodyToSend; + if (data instanceof FormData) { + bodyToSend = data; + // Let fetch set correct Content-Type with boundary + if (headers['Content-Type']) delete headers['Content-Type']; + } else { + bodyToSend = data ? JSON.stringify(data) : undefined; + } // Auto-include token for authenticated requests if (requiresAuth) { @@ -45,7 +53,7 @@ class AuthService { const response = await fetch(fullUrl, { method, headers, - body: data ? JSON.stringify(data) : undefined, + body: bodyToSend, }); const endTime = Date.now(); @@ -189,7 +197,7 @@ class AuthService { let serie = passportSerie; let pid = passportId; if (!serie || !pid) { - const user = await this.getStoredUser(); + const user = await this.getStoredUser(); serie = serie || user?.passport_serie; pid = pid || user?.passport_id; } @@ -214,7 +222,7 @@ class AuthService { let serie = passportSerie; let pid = passportId; if (!serie || !pid) { - const user = await this.getStoredUser(); + const user = await this.getStoredUser(); serie = serie || user?.passport_serie; pid = pid || user?.passport_id; } @@ -491,6 +499,41 @@ class AuthService { async downloadCardBalances(orderId) { return this.makeRequest(`/card-balances-download/${orderId}`, null, true, 'GET'); } + + // ================================ + // Card Requisites Orders (Kart rekwizitler) + // ================================ + + // LIST + async getCardRequisiteOrders() { + return this.makeRequest('/card-requisites', null, true, 'GET'); + } + + // CREATE (passport + card info) + async createCardRequisiteOrder(payload) { + /* payload expected keys: passport_serie, passport_id, card_number, card_month, card_year, maybe card_name */ + return this.makeRequest('/card-requisites', payload, true, 'POST'); + } + + // SHOW + async getCardRequisiteOrder(orderId) { + return this.makeRequest(`/card-requisites/${orderId}`, null, true, 'GET'); + } + + // UPDATE + async updateCardRequisiteOrder(orderId, payload) { + return this.makeRequest(`/card-requisites/${orderId}`, payload, true, 'POST'); + } + + // DELETE + async deleteCardRequisiteOrder(orderId) { + return this.makeRequest(`/card-requisites/${orderId}`, null, true, 'DELETE'); + } + + // DOWNLOAD + async downloadCardRequisites(orderId) { + return this.makeRequest(`/card-requisites-download/${orderId}`, null, true, 'GET'); + } } export default new AuthService(); \ No newline at end of file