Loan paid off letter
This commit is contained in:
221
src/screens/Loan/CreateLoanPaidOffLetterOrderScreen.js
Normal file
221
src/screens/Loan/CreateLoanPaidOffLetterOrderScreen.js
Normal file
@@ -0,0 +1,221 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, ScrollView, SafeAreaView } from 'react-native';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { COLORS } from '../../constants/colors';
|
||||
import Input from '../../components/Input';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import apiService from '../../services/apiService';
|
||||
|
||||
const CreateLoanPaidOffLetterOrderScreen = () => {
|
||||
const navigation = useNavigation();
|
||||
|
||||
// Form states (required fields only)
|
||||
const [region, setRegion] = useState('');
|
||||
const [branchId, setBranchId] = useState('');
|
||||
const [customerName, setCustomerName] = useState('');
|
||||
const [customerSurname, setCustomerSurname] = useState('');
|
||||
const [passportSerie, setPassportSerie] = useState('');
|
||||
const [passportId, setPassportId] = useState('');
|
||||
const [bornAt, setBornAt] = useState('');
|
||||
const [phone, setPhone] = useState('');
|
||||
const [contractNumber, setContractNumber] = useState('');
|
||||
const [contractDate, setContractDate] = useState('');
|
||||
const [loanAmount, setLoanAmount] = useState('');
|
||||
const [loanReason, setLoanReason] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
// Basic validation – ensure all required fields are filled
|
||||
if (
|
||||
!region ||
|
||||
!branchId ||
|
||||
!customerName ||
|
||||
!customerSurname ||
|
||||
!passportSerie ||
|
||||
!passportId ||
|
||||
!bornAt ||
|
||||
!phone ||
|
||||
!contractNumber ||
|
||||
!contractDate ||
|
||||
!loanAmount ||
|
||||
!loanReason
|
||||
) {
|
||||
Alert.alert('Error', 'Please fill in all required fields');
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
region,
|
||||
branch_id: parseInt(branchId),
|
||||
customer_name: customerName,
|
||||
customer_surname: customerSurname,
|
||||
passport_serie: passportSerie,
|
||||
passport_id: parseInt(passportId),
|
||||
born_at: bornAt,
|
||||
phone: parseInt(phone),
|
||||
loan_contract_number: contractNumber,
|
||||
loan_contract_date: contractDate,
|
||||
loan_amount: loanAmount,
|
||||
loan_reason: loanReason,
|
||||
};
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const res = await apiService.createLoanPaidOffLetterOrder(payload);
|
||||
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');
|
||||
}
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
Alert.alert('Error', error.message || '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>
|
||||
|
||||
<ScrollView contentContainerStyle={{ paddingBottom: 40 }} showsVerticalScrollIndicator={false}>
|
||||
<Text style={styles.title}>Täze güwanama sargyt et</Text>
|
||||
|
||||
{/* Region & Branch */}
|
||||
<Input
|
||||
label="Region (ag, ak, mr, ... )"
|
||||
placeholder="mr"
|
||||
value={region}
|
||||
onChangeText={setRegion}
|
||||
autoCapitalize="none"
|
||||
/>
|
||||
<Input
|
||||
label="Şahamça ID-si"
|
||||
placeholder="12"
|
||||
value={branchId}
|
||||
onChangeText={setBranchId}
|
||||
keyboardType="numeric"
|
||||
/>
|
||||
|
||||
{/* Customer */}
|
||||
<Input
|
||||
label="Ady"
|
||||
placeholder="Mahmyt"
|
||||
value={customerName}
|
||||
onChangeText={setCustomerName}
|
||||
/>
|
||||
<Input
|
||||
label="Familiýasy"
|
||||
placeholder="Allaberdiyev"
|
||||
value={customerSurname}
|
||||
onChangeText={setCustomerSurname}
|
||||
/>
|
||||
|
||||
{/* Passport */}
|
||||
<Input
|
||||
label="Passport seriýasy"
|
||||
placeholder="I-AS"
|
||||
value={passportSerie}
|
||||
onChangeText={setPassportSerie}
|
||||
autoCapitalize="characters"
|
||||
/>
|
||||
<Input
|
||||
label="Passport nomeri"
|
||||
placeholder="100999"
|
||||
value={passportId}
|
||||
onChangeText={setPassportId}
|
||||
keyboardType="numeric"
|
||||
/>
|
||||
|
||||
{/* Other personal */}
|
||||
<Input
|
||||
label="Doglan senesi (DD.MM.YYYY)"
|
||||
placeholder="10.10.2000"
|
||||
value={bornAt}
|
||||
onChangeText={setBornAt}
|
||||
/>
|
||||
<Input
|
||||
label="Telefon belgi (+9936...)"
|
||||
placeholder="65999990"
|
||||
value={phone}
|
||||
onChangeText={setPhone}
|
||||
keyboardType="numeric"
|
||||
/>
|
||||
|
||||
{/* Loan contract */}
|
||||
<Input
|
||||
label="Karz şertnama nomeri"
|
||||
placeholder="3242358989234"
|
||||
value={contractNumber}
|
||||
onChangeText={setContractNumber}
|
||||
/>
|
||||
<Input
|
||||
label="Karz şertnama senesi"
|
||||
placeholder="20.04.2022"
|
||||
value={contractDate}
|
||||
onChangeText={setContractDate}
|
||||
/>
|
||||
<Input
|
||||
label="Karz mukdary"
|
||||
placeholder="20000"
|
||||
value={loanAmount}
|
||||
onChangeText={setLoanAmount}
|
||||
keyboardType="numeric"
|
||||
/>
|
||||
<Input
|
||||
label="Karz maksady"
|
||||
placeholder="Puldan pul gazanmak üçin"
|
||||
value={loanReason}
|
||||
onChangeText={setLoanReason}
|
||||
/>
|
||||
|
||||
<TouchableOpacity style={styles.submitBtn} onPress={handleSubmit} disabled={loading}>
|
||||
{loading ? (
|
||||
<ActivityIndicator color={COLORS.white} />
|
||||
) : (
|
||||
<Text style={styles.submitText}>Ýatda sakla</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</ScrollView>
|
||||
</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 CreateLoanPaidOffLetterOrderScreen;
|
||||
148
src/screens/Loan/LoanPaidOffLetterOrderDetailsScreen.js
Normal file
148
src/screens/Loan/LoanPaidOffLetterOrderDetailsScreen.js
Normal file
@@ -0,0 +1,148 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, ScrollView, SafeAreaView } 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';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
|
||||
const LoanPaidOffLetterOrderDetailsScreen = () => {
|
||||
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.getLoanPaidOffLetterOrder(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.deleteLoanPaidOffLetterOrder(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 (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<StatusBar style="dark" />
|
||||
<TouchableOpacity style={styles.backBtn} onPress={() => navigation.goBack()}>
|
||||
<Ionicons name="close" size={28} color={COLORS.textPrimary} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<ScrollView contentContainerStyle={{ paddingBottom: 40 }}>
|
||||
<Text style={styles.title}>Güwanama 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>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
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 LoanPaidOffLetterOrderDetailsScreen;
|
||||
145
src/screens/Loan/LoanPaidOffLetterOrdersScreen.js
Normal file
145
src/screens/Loan/LoanPaidOffLetterOrdersScreen.js
Normal file
@@ -0,0 +1,145 @@
|
||||
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 apiService from '../../services/apiService';
|
||||
import { COLORS } from '../../constants/colors';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
|
||||
const CARD_BG = '#F1F9F1';
|
||||
const CIRCLE_BG = '#A2E4A4';
|
||||
|
||||
const LoanPaidOffLetterOrdersScreen = () => {
|
||||
const navigation = useNavigation();
|
||||
const [orders, setOrders] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
const fetchOrders = async () => {
|
||||
try {
|
||||
const res = await apiService.getLoanPaidOffLetterOrders();
|
||||
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 renderItem = ({ item }) => {
|
||||
const passportLine = `Pasport: ${item.passport_serie} ${item.passport_id}`;
|
||||
const accountLine = `Karz hasaby:`;
|
||||
const created = item.created_at ? new Date(item.created_at).toLocaleDateString() : '';
|
||||
|
||||
return (
|
||||
<TouchableOpacity style={styles.card} onPress={() => navigation.navigate('LoanPaidOffLetterOrderDetails', { orderId: item.id })}>
|
||||
<View style={styles.circle}>
|
||||
<Text style={styles.circleText}>{item.id}</Text>
|
||||
</View>
|
||||
<View style={styles.cardContent}>
|
||||
<Text style={styles.passportText}>{passportLine}</Text>
|
||||
<Text style={styles.accountLabel}>{accountLine}</Text>
|
||||
<Text style={styles.accountValue}>{item.loan_contract_number || '-'}</Text>
|
||||
<Text style={styles.dateText}>{created}</Text>
|
||||
</View>
|
||||
</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}>Ýapylandygy barada güwanamalar</Text>
|
||||
</View>
|
||||
|
||||
<FlatList
|
||||
data={orders}
|
||||
keyExtractor={(it) => it.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>}
|
||||
/>
|
||||
|
||||
<TouchableOpacity style={styles.fab} onPress={() => navigation.navigate('CreateLoanPaidOffLetterOrder')}>
|
||||
<Ionicons name="add" size={28} color={COLORS.white} />
|
||||
</TouchableOpacity>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: COLORS.backgroundSecondary },
|
||||
centered: { flex: 1, alignItems: 'center', justifyContent: 'center' },
|
||||
header: { flexDirection: 'row', alignItems: 'center', padding: 16 },
|
||||
headerTitle: { fontSize: 18, fontWeight: 'bold', color: COLORS.textPrimary, marginLeft: 12 },
|
||||
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 },
|
||||
passportText: { fontWeight: '700', color: COLORS.textPrimary, marginBottom: 4 },
|
||||
accountLabel: { color: COLORS.textSecondary, fontSize: 14 },
|
||||
accountValue: { 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',
|
||||
right: 24,
|
||||
bottom: 40,
|
||||
width: 56,
|
||||
height: 56,
|
||||
borderRadius: 28,
|
||||
backgroundColor: COLORS.primary,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
elevation: 4,
|
||||
},
|
||||
});
|
||||
|
||||
export default LoanPaidOffLetterOrdersScreen;
|
||||
Reference in New Issue
Block a user