few changes
This commit is contained in:
@@ -4,7 +4,7 @@ import { Ionicons } from '@expo/vector-icons';
|
|||||||
import { COLORS } from '../constants/colors';
|
import { COLORS } from '../constants/colors';
|
||||||
|
|
||||||
import HomeScreen from '../screens/Main/HomeScreen';
|
import HomeScreen from '../screens/Main/HomeScreen';
|
||||||
import MenuScreen from '../screens/Main/MenuScreen';
|
import MenuNavigator from './MenuNavigator';
|
||||||
import ProfileScreen from '../screens/Main/ProfileScreen';
|
import ProfileScreen from '../screens/Main/ProfileScreen';
|
||||||
|
|
||||||
const Tab = createBottomTabNavigator();
|
const Tab = createBottomTabNavigator();
|
||||||
@@ -56,7 +56,7 @@ const MainNavigator = () => {
|
|||||||
/>
|
/>
|
||||||
<Tab.Screen
|
<Tab.Screen
|
||||||
name="Menu"
|
name="Menu"
|
||||||
component={MenuScreen}
|
component={MenuNavigator}
|
||||||
options={{
|
options={{
|
||||||
tabBarLabel: 'Hyzmatlar',
|
tabBarLabel: 'Hyzmatlar',
|
||||||
}}
|
}}
|
||||||
|
|||||||
23
src/navigation/MenuNavigator.js
Normal file
23
src/navigation/MenuNavigator.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { createStackNavigator } from '@react-navigation/stack';
|
||||||
|
import MenuScreen from '../screens/Main/MenuScreen';
|
||||||
|
import LoanRemainingOrdersScreen from '../screens/Loan/LoanRemainingOrdersScreen';
|
||||||
|
import CreateLoanRemainingOrderScreen from '../screens/Loan/CreateLoanRemainingOrderScreen';
|
||||||
|
import LoanPaidOffLetterOrdersScreen from '../screens/Loan/LoanPaidOffLetterOrdersScreen';
|
||||||
|
import CreateLoanPaidOffLetterOrderScreen from '../screens/Loan/CreateLoanPaidOffLetterOrderScreen';
|
||||||
|
import LoanPaidOffLetterOrderDetailsScreen from '../screens/Loan/LoanPaidOffLetterOrderDetailsScreen';
|
||||||
|
|
||||||
|
const Stack = createStackNavigator();
|
||||||
|
|
||||||
|
const MenuNavigator = () => (
|
||||||
|
<Stack.Navigator screenOptions={{ headerShown: false }}>
|
||||||
|
<Stack.Screen name="MenuHome" component={MenuScreen} />
|
||||||
|
<Stack.Screen name="LoanRemainingOrders" component={LoanRemainingOrdersScreen} />
|
||||||
|
<Stack.Screen name="CreateLoanRemainingOrder" component={CreateLoanRemainingOrderScreen} />
|
||||||
|
<Stack.Screen name="LoanPaidOffLetterOrders" component={LoanPaidOffLetterOrdersScreen} />
|
||||||
|
<Stack.Screen name="CreateLoanPaidOffLetterOrder" component={CreateLoanPaidOffLetterOrderScreen} />
|
||||||
|
<Stack.Screen name="LoanPaidOffLetterOrderDetails" component={LoanPaidOffLetterOrderDetailsScreen} />
|
||||||
|
</Stack.Navigator>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default MenuNavigator;
|
||||||
93
src/screens/Loan/CreateLoanRemainingOrderScreen.js
Normal file
93
src/screens/Loan/CreateLoanRemainingOrderScreen.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, SafeAreaView } from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import apiService from '../../services/apiService';
|
||||||
|
import { COLORS } from '../../constants/colors';
|
||||||
|
import Input from '../../components/Input';
|
||||||
|
import { StatusBar } from 'expo-status-bar';
|
||||||
|
|
||||||
|
const CreateLoanRemainingOrderScreen = () => {
|
||||||
|
const navigation = useNavigation();
|
||||||
|
const [accountNumber, setAccountNumber] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (accountNumber.trim().length === 0) {
|
||||||
|
Alert.alert('Error', 'Account number is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading(true);
|
||||||
|
const res = await apiService.createLoanRemainingOrder(accountNumber.trim());
|
||||||
|
setLoading(false);
|
||||||
|
if (res.success) {
|
||||||
|
Alert.alert('Success', res.message || 'Order created successfully', [
|
||||||
|
{ text: 'OK', onPress: () => navigation.goBack() },
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
Alert.alert('Error', res.error || 'Could not create order');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<StatusBar style="dark" />
|
||||||
|
<TouchableOpacity style={styles.backBtn} onPress={() => navigation.goBack()}>
|
||||||
|
<Ionicons name="arrow-back" size={24} color={COLORS.textPrimary} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<Text style={styles.title}>Täze sargyt</Text>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
label="Account number"
|
||||||
|
placeholder="1420..."
|
||||||
|
value={accountNumber}
|
||||||
|
onChangeText={setAccountNumber}
|
||||||
|
keyboardType="numeric"
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoCorrect={false}
|
||||||
|
returnKeyType="done"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TouchableOpacity style={styles.submitBtn} onPress={handleSubmit} disabled={loading}>
|
||||||
|
{loading ? (
|
||||||
|
<ActivityIndicator color={COLORS.white} />
|
||||||
|
) : (
|
||||||
|
<Text style={styles.submitText}>Ýatda sakla</Text>
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: COLORS.backgroundSecondary,
|
||||||
|
paddingHorizontal: 24,
|
||||||
|
paddingTop: 40,
|
||||||
|
},
|
||||||
|
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 CreateLoanRemainingOrderScreen;
|
||||||
144
src/screens/Loan/LoanRemainingOrderDetailsScreen.js
Normal file
144
src/screens/Loan/LoanRemainingOrderDetailsScreen.js
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert } from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import { useNavigation, useRoute } from '@react-navigation/native';
|
||||||
|
import apiService from '../../services/apiService';
|
||||||
|
import { COLORS } from '../../constants/colors';
|
||||||
|
|
||||||
|
const LoanRemainingOrderDetailsScreen = () => {
|
||||||
|
const navigation = useNavigation();
|
||||||
|
const route = useRoute();
|
||||||
|
const { orderId } = route.params || {};
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [order, setOrder] = useState(null);
|
||||||
|
|
||||||
|
const fetchDetails = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const res = await apiService.getLoanRemainingOrder(orderId);
|
||||||
|
if (res.success) {
|
||||||
|
setOrder(res.data);
|
||||||
|
} else {
|
||||||
|
Alert.alert('Error', res.error || 'Could not fetch details');
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchDetails();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
Alert.alert('Confirm', 'Are you sure you want to delete this order?', [
|
||||||
|
{ text: 'Cancel', style: 'cancel' },
|
||||||
|
{ text: 'Delete', style: 'destructive', onPress: deleteOrder },
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteOrder = async () => {
|
||||||
|
const res = await apiService.deleteLoanRemainingOrder(orderId);
|
||||||
|
if (res.success) {
|
||||||
|
Alert.alert('Deleted', res.message || 'Order deleted', [
|
||||||
|
{ text: 'OK', onPress: () => navigation.goBack() },
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
Alert.alert('Error', res.error || 'Could not delete');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<View style={styles.centered}>
|
||||||
|
<ActivityIndicator size="large" color={COLORS.primary} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!order) {
|
||||||
|
return (
|
||||||
|
<View style={styles.centered}>
|
||||||
|
<Text>No data</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<TouchableOpacity style={styles.backBtn} onPress={() => navigation.goBack()}>
|
||||||
|
<Ionicons name="close" size={28} color={COLORS.textPrimary} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<Text style={styles.title}>Galyndy detallary</Text>
|
||||||
|
|
||||||
|
<View style={styles.detailCard}>
|
||||||
|
{Object.entries(order).map(([key, value]) => (
|
||||||
|
<View key={key} style={styles.detailRow}>
|
||||||
|
<Text style={styles.detailKey}>{key}</Text>
|
||||||
|
<Text style={styles.detailValue}>{String(value)}</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TouchableOpacity style={styles.deleteBtn} onPress={handleDelete}>
|
||||||
|
<Text style={styles.deleteText}>Poz</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: COLORS.backgroundSecondary,
|
||||||
|
paddingHorizontal: 24,
|
||||||
|
paddingTop: 40,
|
||||||
|
},
|
||||||
|
centered: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
backBtn: {
|
||||||
|
alignSelf: 'flex-end',
|
||||||
|
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',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
detailKey: {
|
||||||
|
fontWeight: '600',
|
||||||
|
color: COLORS.textSecondary,
|
||||||
|
},
|
||||||
|
detailValue: {
|
||||||
|
color: COLORS.textPrimary,
|
||||||
|
maxWidth: '60%',
|
||||||
|
textAlign: 'right',
|
||||||
|
},
|
||||||
|
deleteBtn: {
|
||||||
|
backgroundColor: COLORS.error,
|
||||||
|
paddingVertical: 14,
|
||||||
|
borderRadius: 8,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
deleteText: {
|
||||||
|
color: COLORS.white,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default LoanRemainingOrderDetailsScreen;
|
||||||
309
src/screens/Loan/LoanRemainingOrdersScreen.js
Normal file
309
src/screens/Loan/LoanRemainingOrdersScreen.js
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import { View, Text, StyleSheet, FlatList, ActivityIndicator, TouchableOpacity, RefreshControl, Modal, ScrollView, Alert, SafeAreaView, Pressable } from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import { useNavigation, useFocusEffect } from '@react-navigation/native';
|
||||||
|
import apiService from '../../services/apiService';
|
||||||
|
import { COLORS } from '../../constants/colors';
|
||||||
|
import { StatusBar } from 'expo-status-bar';
|
||||||
|
|
||||||
|
const LoanRemainingOrdersScreen = () => {
|
||||||
|
const navigation = useNavigation();
|
||||||
|
const [orders, setOrders] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
|
const [modalData, setModalData] = useState(null);
|
||||||
|
const [modalLoading, setModalLoading] = useState(false);
|
||||||
|
|
||||||
|
const fetchOrders = async () => {
|
||||||
|
try {
|
||||||
|
const res = await apiService.getLoanRemainingOrders();
|
||||||
|
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 = async (item) => {
|
||||||
|
setModalLoading(true);
|
||||||
|
setModalVisible(true);
|
||||||
|
|
||||||
|
const res = await apiService.getLoanRemainingBalance(item.account_number, item.passport_serie, item.passport_id);
|
||||||
|
if (res.success) {
|
||||||
|
setModalData(res.data);
|
||||||
|
} else {
|
||||||
|
setModalData(null);
|
||||||
|
Alert.alert('Info', res.error || 'Not found');
|
||||||
|
setModalVisible(false);
|
||||||
|
}
|
||||||
|
setModalLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderItem = ({ item }) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.card}
|
||||||
|
onPress={() => handleItemPress(item)}
|
||||||
|
>
|
||||||
|
<View style={styles.cardIconWrapper}>
|
||||||
|
<Ionicons name="stats-chart" size={24} color={COLORS.primary} />
|
||||||
|
</View>
|
||||||
|
<View style={styles.cardContent}>
|
||||||
|
<Text style={styles.cardTitle}>{item.account_number}</Text>
|
||||||
|
</View>
|
||||||
|
<Ionicons name="information-circle" size={20} color={COLORS.gray[400]} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<View style={styles.centered}>
|
||||||
|
<ActivityIndicator size="large" color={COLORS.primary} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<StatusBar style="dark" />
|
||||||
|
{/* Header */}
|
||||||
|
<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}>Karzyň galyndysy</Text>
|
||||||
|
</View>
|
||||||
|
<FlatList
|
||||||
|
data={orders}
|
||||||
|
keyExtractor={(item) => item.id?.toString()}
|
||||||
|
renderItem={renderItem}
|
||||||
|
contentContainerStyle={orders.length === 0 && styles.emptyContainer}
|
||||||
|
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
|
||||||
|
ListEmptyComponent={<Text style={styles.emptyText}>No orders yet</Text>}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Floating Action Button */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.fab}
|
||||||
|
onPress={() => navigation.navigate('CreateLoanRemainingOrder')}
|
||||||
|
>
|
||||||
|
<Ionicons name="add" size={28} color={COLORS.white} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* Result Modal */}
|
||||||
|
<Modal
|
||||||
|
visible={modalVisible}
|
||||||
|
transparent
|
||||||
|
animationType="fade"
|
||||||
|
onRequestClose={() => setModalVisible(false)}
|
||||||
|
>
|
||||||
|
<Pressable style={styles.modalBackdrop} onPress={() => setModalVisible(false)}>
|
||||||
|
<Pressable style={styles.modalCard} onPress={(e) => e.stopPropagation()}>
|
||||||
|
<TouchableOpacity style={styles.modalCloseSmall} onPress={() => setModalVisible(false)}>
|
||||||
|
<Ionicons name="close" size={20} color={COLORS.textPrimary} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text style={styles.modalTitle}>Netije</Text>
|
||||||
|
{modalLoading ? (
|
||||||
|
<View style={{ paddingVertical: 40, alignItems: 'center' }}>
|
||||||
|
<ActivityIndicator size="large" color={COLORS.primary} />
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<ScrollView
|
||||||
|
style={{ maxHeight: 320, marginTop: 8 }}
|
||||||
|
contentContainerStyle={{ paddingBottom: 16 }}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
>
|
||||||
|
{modalData && (() => {
|
||||||
|
const fields = [
|
||||||
|
{ key: 'branchName', label: 'Şahamça' },
|
||||||
|
{ key: 'clientName', label: 'Müşderi' },
|
||||||
|
{ key: 'docNum', label: 'Şertnama belgisi' },
|
||||||
|
{ key: '__divider1' },
|
||||||
|
{ key: 'docSum', label: 'Jemi karzyň möçberi' },
|
||||||
|
{ key: 'balans', label: 'Karz boýunça jemi galyndy' },
|
||||||
|
{ key: 'percentBalance', label: 'Hasaplama göterim (şu aý üçin)' },
|
||||||
|
{ key: 'docMonthSum', label: 'Hasaplanan esasy bergi (şu aý üçin)' },
|
||||||
|
{ key: 'docPayed', label: 'Jemi tölenen möçberi' },
|
||||||
|
];
|
||||||
|
|
||||||
|
let renderedCount = 0;
|
||||||
|
const rows = fields.map((f) => {
|
||||||
|
if (f.key.startsWith('__divider')) {
|
||||||
|
return <View key={f.key} style={styles.divider} />;
|
||||||
|
}
|
||||||
|
const val = modalData[f.key];
|
||||||
|
if (val === null || val === undefined || val === '') return null;
|
||||||
|
renderedCount++;
|
||||||
|
return (
|
||||||
|
<View key={f.key} style={styles.itemWrap}>
|
||||||
|
<Text style={styles.itemLabel}>{f.label}</Text>
|
||||||
|
<Text style={styles.itemValue}>{String(val)}</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (renderedCount === 0) {
|
||||||
|
return Object.entries(modalData).map(([k, v]) => (
|
||||||
|
<View key={k} style={styles.itemWrap}>
|
||||||
|
<Text style={styles.itemLabel}>{k}</Text>
|
||||||
|
<Text style={styles.itemValue}>{String(v)}</Text>
|
||||||
|
</View>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
})()}
|
||||||
|
</ScrollView>
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
|
</Pressable>
|
||||||
|
</Modal>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: COLORS.backgroundSecondary,
|
||||||
|
},
|
||||||
|
centered: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: 20,
|
||||||
|
borderRadius: 10,
|
||||||
|
backgroundColor: COLORS.white,
|
||||||
|
marginHorizontal: 24,
|
||||||
|
marginTop: 16,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 1 },
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
shadowRadius: 3,
|
||||||
|
elevation: 2,
|
||||||
|
},
|
||||||
|
cardIconWrapper: {
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 20,
|
||||||
|
backgroundColor: COLORS.backgroundSecondary,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginRight: 16,
|
||||||
|
},
|
||||||
|
cardContent: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
cardTitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: COLORS.textPrimary,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
cardSubtitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: COLORS.textSecondary,
|
||||||
|
},
|
||||||
|
emptyContainer: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
emptyText: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: COLORS.textSecondary,
|
||||||
|
},
|
||||||
|
fab: {
|
||||||
|
position: 'absolute',
|
||||||
|
right: 24,
|
||||||
|
bottom: 40,
|
||||||
|
width: 56,
|
||||||
|
height: 56,
|
||||||
|
borderRadius: 28,
|
||||||
|
backgroundColor: COLORS.primary,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.3,
|
||||||
|
shadowRadius: 4,
|
||||||
|
elevation: 4,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: 16,
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: COLORS.textPrimary,
|
||||||
|
marginLeft: 12,
|
||||||
|
},
|
||||||
|
modalBackdrop: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.4)',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingHorizontal: 24,
|
||||||
|
},
|
||||||
|
modalCard: {
|
||||||
|
width: '100%',
|
||||||
|
backgroundColor: COLORS.white,
|
||||||
|
borderRadius: 12,
|
||||||
|
padding: 20,
|
||||||
|
elevation: 5,
|
||||||
|
},
|
||||||
|
modalCloseSmall: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 8,
|
||||||
|
right: 8,
|
||||||
|
},
|
||||||
|
modalTitle: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: COLORS.textPrimary,
|
||||||
|
marginBottom: 16,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
divider: {
|
||||||
|
height: 1,
|
||||||
|
backgroundColor: COLORS.gray[200],
|
||||||
|
marginVertical: 12,
|
||||||
|
},
|
||||||
|
itemWrap: {
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
itemLabel: {
|
||||||
|
fontSize: 15,
|
||||||
|
color: COLORS.textSecondary,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
itemValue: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: COLORS.textPrimary,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default LoanRemainingOrdersScreen;
|
||||||
@@ -101,6 +101,7 @@ const styles = StyleSheet.create({
|
|||||||
paddingTop: 16,
|
paddingTop: 16,
|
||||||
paddingBottom: 24,
|
paddingBottom: 24,
|
||||||
backgroundColor: COLORS.white,
|
backgroundColor: COLORS.white,
|
||||||
|
marginBottom: 16,
|
||||||
},
|
},
|
||||||
greeting: {
|
greeting: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
Text,
|
Text,
|
||||||
@@ -12,6 +13,8 @@ import { Ionicons } from '@expo/vector-icons';
|
|||||||
import { COLORS } from '../../constants/colors';
|
import { COLORS } from '../../constants/colors';
|
||||||
|
|
||||||
const MenuScreen = () => {
|
const MenuScreen = () => {
|
||||||
|
const navigation = useNavigation();
|
||||||
|
|
||||||
const menuSections = [
|
const menuSections = [
|
||||||
{
|
{
|
||||||
title: 'Karz',
|
title: 'Karz',
|
||||||
@@ -41,8 +44,13 @@ const MenuScreen = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const handleMenuItemPress = (item) => {
|
const handleMenuItemPress = (item) => {
|
||||||
console.log('Menu item pressed:', item.title);
|
if (item.id === 2) {
|
||||||
// Handle navigation or action here
|
navigation.navigate('LoanRemainingOrders');
|
||||||
|
} else if (item.id === 3) {
|
||||||
|
navigation.navigate('LoanPaidOffLetterOrders');
|
||||||
|
} else {
|
||||||
|
console.log('Menu item pressed:', item.title);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -93,6 +93,127 @@ class ApiService {
|
|||||||
return authService.unblockCard(cardId);
|
return authService.unblockCard(cardId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// Loan Remaining Order (Karzyň galyndysy)
|
||||||
|
// ================================
|
||||||
|
|
||||||
|
async getLoanRemainingOrders() {
|
||||||
|
try {
|
||||||
|
const data = await authService.getLoanRemainingOrders();
|
||||||
|
return { success: true, data };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createLoanRemainingOrder(accountNumber) {
|
||||||
|
try {
|
||||||
|
const response = await authService.createLoanRemainingOrder(accountNumber);
|
||||||
|
return { success: true, message: response.message };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLoanRemainingOrder(orderId) {
|
||||||
|
try {
|
||||||
|
const data = await authService.getLoanRemainingOrder(orderId);
|
||||||
|
return { success: true, data };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateLoanRemainingOrder(orderId, accountNumber) {
|
||||||
|
try {
|
||||||
|
const response = await authService.updateLoanRemainingOrder(orderId, accountNumber);
|
||||||
|
return { success: true, message: response.message };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteLoanRemainingOrder(orderId) {
|
||||||
|
try {
|
||||||
|
const response = await authService.deleteLoanRemainingOrder(orderId);
|
||||||
|
return { success: true, message: response.message };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quick loan remaining check
|
||||||
|
async getLoanRemainingBalance(accountNumber, passportSerie = null, passportId = null) {
|
||||||
|
try {
|
||||||
|
let raw = await authService.getLoanRemainingBalance(accountNumber, passportSerie, passportId);
|
||||||
|
|
||||||
|
// Some endpoints return JSON-string (e.g. "{\"branchName\":...}") instead of object
|
||||||
|
if (typeof raw === 'string') {
|
||||||
|
try {
|
||||||
|
raw = JSON.parse(raw);
|
||||||
|
} catch (_) {
|
||||||
|
// leave as-is
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (raw && raw.errCode && raw.errCode > 0) {
|
||||||
|
return { success: false, error: raw.message || 'Not found', data: raw };
|
||||||
|
}
|
||||||
|
return { success: true, data: raw };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// Loan Paid-Off Letter Orders
|
||||||
|
// ================================
|
||||||
|
|
||||||
|
async getLoanPaidOffLetterOrders() {
|
||||||
|
try {
|
||||||
|
const data = await authService.getLoanPaidOffLetterOrders();
|
||||||
|
return { success: true, data };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createLoanPaidOffLetterOrder(payload) {
|
||||||
|
try {
|
||||||
|
const response = await authService.createLoanPaidOffLetterOrder(payload);
|
||||||
|
return { success: true, message: response.message };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLoanPaidOffLetterOrder(orderId) {
|
||||||
|
try {
|
||||||
|
const data = await authService.getLoanPaidOffLetterOrder(orderId);
|
||||||
|
return { success: true, data };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateLoanPaidOffLetterOrder(orderId, payload) {
|
||||||
|
try {
|
||||||
|
const response = await authService.updateLoanPaidOffLetterOrder(orderId, payload);
|
||||||
|
return { success: true, message: response.message };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteLoanPaidOffLetterOrder(orderId) {
|
||||||
|
try {
|
||||||
|
const response = await authService.deleteLoanPaidOffLetterOrder(orderId);
|
||||||
|
return { success: true, message: response.message };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Utility methods for common operations
|
// Utility methods for common operations
|
||||||
async getUserDashboardData() {
|
async getUserDashboardData() {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -164,6 +164,114 @@ class AuthService {
|
|||||||
async deleteAccount() {
|
async deleteAccount() {
|
||||||
return this.makeRequest('/user/delete-account', null, true, 'DELETE');
|
return this.makeRequest('/user/delete-account', null, true, 'DELETE');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// Loan Remaining Order (Karzyň galyndysy)
|
||||||
|
// ================================
|
||||||
|
|
||||||
|
// Helper to read cached user data (for passport details)
|
||||||
|
async getStoredUser() {
|
||||||
|
try {
|
||||||
|
const raw = await AsyncStorage.getItem('user_data');
|
||||||
|
return raw ? JSON.parse(raw) : null;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LIST orders
|
||||||
|
async getLoanRemainingOrders() {
|
||||||
|
return this.makeRequest('/loan-remaining-order', null, true, 'GET');
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREATE order (requires only account number – passport details are fetched from user profile)
|
||||||
|
async createLoanRemainingOrder(accountNumber) {
|
||||||
|
const user = await this.getStoredUser();
|
||||||
|
if (!user?.passport_serie || !user?.passport_id) {
|
||||||
|
throw new Error('Passport details are missing from profile');
|
||||||
|
}
|
||||||
|
const payload = {
|
||||||
|
passport_serie: user.passport_serie,
|
||||||
|
passport_id: user.passport_id,
|
||||||
|
account_number: accountNumber,
|
||||||
|
};
|
||||||
|
return this.makeRequest('/loan-remaining-order', payload, true, 'POST');
|
||||||
|
}
|
||||||
|
|
||||||
|
// SHOW order details
|
||||||
|
async getLoanRemainingOrder(orderId) {
|
||||||
|
return this.makeRequest(`/loan-remaining-order/${orderId}`, null, true, 'GET');
|
||||||
|
}
|
||||||
|
|
||||||
|
// UPDATE order (only account number can change; passport details stay the same)
|
||||||
|
async updateLoanRemainingOrder(orderId, accountNumber) {
|
||||||
|
const user = await this.getStoredUser();
|
||||||
|
if (!user?.passport_serie || !user?.passport_id) {
|
||||||
|
throw new Error('Passport details are missing from profile');
|
||||||
|
}
|
||||||
|
const payload = {
|
||||||
|
passport_serie: user.passport_serie,
|
||||||
|
passport_id: user.passport_id,
|
||||||
|
account_number: accountNumber,
|
||||||
|
};
|
||||||
|
return this.makeRequest(`/loan-remaining-order/${orderId}`, payload, true, 'POST');
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE order
|
||||||
|
async deleteLoanRemainingOrder(orderId) {
|
||||||
|
return this.makeRequest(`/loan-remaining-order/${orderId}`, null, true, 'DELETE');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// Loan remaining quick check (returns remaining balance info)
|
||||||
|
// ================================
|
||||||
|
|
||||||
|
async getLoanRemainingBalance(accountNumber, passportSerie = null, passportId = null) {
|
||||||
|
let serie = passportSerie;
|
||||||
|
let pid = passportId;
|
||||||
|
|
||||||
|
if (!serie || !pid) {
|
||||||
|
const user = await this.getStoredUser();
|
||||||
|
serie = user?.passport_serie;
|
||||||
|
pid = user?.passport_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!serie || !pid) {
|
||||||
|
throw new Error('Passport details are missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = `passport_serie=${encodeURIComponent(serie)}&passport_id=${encodeURIComponent(pid)}&account_number=${encodeURIComponent(accountNumber)}`;
|
||||||
|
return this.makeRequest(`/loan-remaining?${query}`, null, true, 'GET');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// Loan Paid-Off Letter Orders
|
||||||
|
// ================================
|
||||||
|
|
||||||
|
// LIST
|
||||||
|
async getLoanPaidOffLetterOrders() {
|
||||||
|
return this.makeRequest('/loan-paid-off-letter-orders', null, true, 'GET');
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREATE
|
||||||
|
async createLoanPaidOffLetterOrder(data) {
|
||||||
|
return this.makeRequest('/loan-paid-off-letter-orders', data, true, 'POST');
|
||||||
|
}
|
||||||
|
|
||||||
|
// SHOW
|
||||||
|
async getLoanPaidOffLetterOrder(orderId) {
|
||||||
|
return this.makeRequest(`/loan-paid-off-letter-orders/${orderId}`, null, true, 'GET');
|
||||||
|
}
|
||||||
|
|
||||||
|
// UPDATE
|
||||||
|
async updateLoanPaidOffLetterOrder(orderId, data) {
|
||||||
|
return this.makeRequest(`/loan-paid-off-letter-orders/${orderId}`, data, true, 'POST');
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE
|
||||||
|
async deleteLoanPaidOffLetterOrder(orderId) {
|
||||||
|
return this.makeRequest(`/loan-paid-off-letter-orders/${orderId}`, null, true, 'DELETE');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new AuthService();
|
export default new AuthService();
|
||||||
Reference in New Issue
Block a user