few changes
This commit is contained in:
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,
|
||||
paddingBottom: 24,
|
||||
backgroundColor: COLORS.white,
|
||||
marginBottom: 16,
|
||||
},
|
||||
greeting: {
|
||||
fontSize: 16,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
@@ -12,6 +13,8 @@ import { Ionicons } from '@expo/vector-icons';
|
||||
import { COLORS } from '../../constants/colors';
|
||||
|
||||
const MenuScreen = () => {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const menuSections = [
|
||||
{
|
||||
title: 'Karz',
|
||||
@@ -41,8 +44,13 @@ const MenuScreen = () => {
|
||||
];
|
||||
|
||||
const handleMenuItemPress = (item) => {
|
||||
console.log('Menu item pressed:', item.title);
|
||||
// Handle navigation or action here
|
||||
if (item.id === 2) {
|
||||
navigation.navigate('LoanRemainingOrders');
|
||||
} else if (item.id === 3) {
|
||||
navigation.navigate('LoanPaidOffLetterOrders');
|
||||
} else {
|
||||
console.log('Menu item pressed:', item.title);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user