diff --git a/package-lock.json b/package-lock.json
index 5a073bd..dd83136 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,8 @@
"dependencies": {
"@expo/vector-icons": "^14.1.0",
"@react-native-async-storage/async-storage": "^2.2.0",
+ "@react-native-community/datetimepicker": "^7.7.0",
+ "@react-native-picker/picker": "^2.4.12",
"@react-navigation/bottom-tabs": "^7.4.2",
"@react-navigation/native": "^7.1.14",
"@react-navigation/stack": "^7.4.2",
@@ -17,6 +19,7 @@
"expo-status-bar": "~2.2.3",
"react": "19.0.0",
"react-native": "0.79.5",
+ "react-native-modal-datetime-picker": "^15.0.1",
"react-native-safe-area-context": "^5.5.1",
"react-native-screens": "^4.11.1",
"react-native-svg": "^15.12.0"
@@ -2136,6 +2139,26 @@
"react-native": "^0.0.0-0 || >=0.65 <1.0"
}
},
+ "node_modules/@react-native-community/datetimepicker": {
+ "version": "7.7.0",
+ "resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-7.7.0.tgz",
+ "integrity": "sha512-nYzZy4DQLRFUzKJShWzRleCaebmCJfZ1lIcFmZgMXJoiVuGJNw3OIGHSWmHhPETh3OhP1RO3to882d7WmDIyrA==",
+ "dependencies": {
+ "invariant": "^2.2.4"
+ }
+ },
+ "node_modules/@react-native-picker/picker": {
+ "version": "2.11.1",
+ "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.11.1.tgz",
+ "integrity": "sha512-ThklnkK4fV3yynnIIRBkxxjxR4IFbdMNJVF6tlLdOJ/zEFUEFUEdXY0KmH0iYzMwY8W4/InWsLiA7AkpAbnexA==",
+ "workspaces": [
+ "example"
+ ],
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/@react-native/assets-registry": {
"version": "0.79.5",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.79.5.tgz",
@@ -6153,6 +6176,21 @@
"node": ">= 6"
}
},
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/prop-types/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -6360,6 +6398,18 @@
"react-native": "*"
}
},
+ "node_modules/react-native-modal-datetime-picker": {
+ "version": "15.0.1",
+ "resolved": "https://registry.npmjs.org/react-native-modal-datetime-picker/-/react-native-modal-datetime-picker-15.0.1.tgz",
+ "integrity": "sha512-FmNFeGwYWH6TCUvAr8OX75tu0FSUDxeEOxCorq2PdeIqbRx9wt7y/5oKUCfbWBWM6y+gme4TzrA0xAtufHqgGg==",
+ "dependencies": {
+ "prop-types": "^15.7.2"
+ },
+ "peerDependencies": {
+ "@react-native-community/datetimepicker": ">=6.7.0",
+ "react-native": ">=0.65.0"
+ }
+ },
"node_modules/react-native-safe-area-context": {
"version": "5.5.1",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.5.1.tgz",
diff --git a/package.json b/package.json
index aea8bc7..62762c1 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,8 @@
"dependencies": {
"@expo/vector-icons": "^14.1.0",
"@react-native-async-storage/async-storage": "^2.2.0",
+ "@react-native-community/datetimepicker": "^7.7.0",
+ "@react-native-picker/picker": "^2.4.12",
"@react-navigation/bottom-tabs": "^7.4.2",
"@react-navigation/native": "^7.1.14",
"@react-navigation/stack": "^7.4.2",
@@ -18,6 +20,7 @@
"expo-status-bar": "~2.2.3",
"react": "19.0.0",
"react-native": "0.79.5",
+ "react-native-modal-datetime-picker": "^15.0.1",
"react-native-safe-area-context": "^5.5.1",
"react-native-screens": "^4.11.1",
"react-native-svg": "^15.12.0"
diff --git a/src/components/DateInput.js b/src/components/DateInput.js
new file mode 100644
index 0000000..720b5bb
--- /dev/null
+++ b/src/components/DateInput.js
@@ -0,0 +1,152 @@
+import React, { useState } from 'react';
+import { View, Text, StyleSheet, TouchableOpacity, Platform, Modal } from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+import { COLORS } from '../constants/colors';
+import DateTimePicker from '@react-native-community/datetimepicker';
+
+const formatDate = (date) => {
+ const day = String(date.getDate()).padStart(2, '0');
+ const month = String(date.getMonth() + 1).padStart(2, '0');
+ const year = date.getFullYear();
+ return `${day}.${month}.${year}`;
+};
+
+const parseDate = (str) => {
+ if (!str) return null;
+ const [day, month, year] = str.split('.');
+ if (!day || !month || !year) return null;
+ return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
+};
+
+const DateInput = ({ label, value, onChange, placeholder = 'Saýla', maximumDate, minimumDate }) => {
+ const [show, setShow] = useState(false);
+ const [tempDate, setTempDate] = useState(value ? (typeof value === 'string' ? parseDate(value) : value) : new Date());
+
+ const open = () => {
+ setTempDate(value ? (typeof value === 'string' ? parseDate(value) : value) : new Date());
+ setShow(true);
+ };
+
+ const close = () => setShow(false);
+
+ const onChangeAndroid = (event, selectedDate) => {
+ close();
+ if (event.type !== 'dismissed' && selectedDate) {
+ onChange(formatDate(selectedDate));
+ }
+ };
+
+ const onConfirmIOS = () => {
+ onChange(formatDate(tempDate));
+ close();
+ };
+
+ return (
+
+ {label && {label}}
+
+ {value || placeholder}
+
+
+
+ {show && Platform.OS === 'android' && (
+
+ )}
+
+ {Platform.OS === 'ios' && (
+
+
+
+ d && setTempDate(d)}
+ maximumDate={maximumDate}
+ minimumDate={minimumDate}
+ textColor={COLORS.textPrimary}
+ themeVariant="light"
+ style={{ width: '100%', backgroundColor: COLORS.white, height: 220 }}
+ />
+
+
+ Cancel
+
+
+ Confirm
+
+
+
+
+
+ )}
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ marginBottom: 20,
+ },
+ label: {
+ fontSize: 14,
+ fontWeight: '600',
+ color: COLORS.textPrimary,
+ marginBottom: 8,
+ },
+ selectBox: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ borderWidth: 1,
+ borderColor: COLORS.gray[300],
+ borderRadius: 12,
+ backgroundColor: COLORS.white,
+ paddingHorizontal: 16,
+ minHeight: 52,
+ },
+ selectText: {
+ fontSize: 16,
+ color: COLORS.textPrimary,
+ },
+ modalOverlay: {
+ flex: 1,
+ backgroundColor: 'rgba(0,0,0,0.25)',
+ justifyContent: 'center',
+ paddingHorizontal: 24,
+ },
+ modalContent: {
+ backgroundColor: COLORS.white,
+ borderRadius: 12,
+ overflow: 'hidden',
+ },
+ modalButtons: {
+ flexDirection: 'row',
+ borderTopWidth: 1,
+ borderTopColor: COLORS.gray[200],
+ },
+ modalBtn: {
+ flex: 1,
+ paddingVertical: 14,
+ alignItems: 'center',
+ },
+ modalBtnText: {
+ color: COLORS.primary,
+ fontWeight: '600',
+ fontSize: 16,
+ },
+ modalBtnTextCancel: {
+ color: COLORS.error,
+ fontWeight: '600',
+ fontSize: 16,
+ },
+});
+
+export default DateInput;
\ No newline at end of file
diff --git a/src/components/SelectInput.js b/src/components/SelectInput.js
new file mode 100644
index 0000000..a185bbf
--- /dev/null
+++ b/src/components/SelectInput.js
@@ -0,0 +1,154 @@
+import React, { useState } from 'react';
+import {
+ View,
+ Text,
+ StyleSheet,
+ TouchableOpacity,
+ Modal,
+ FlatList,
+ TouchableWithoutFeedback,
+} from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+import { COLORS } from '../constants/colors';
+
+/**
+ * SelectInput – simple dropdown selector that mimics the look of Input component.
+ *
+ * Props:
+ * - label : string – Field label
+ * - value : any – Currently selected value
+ * - options : Array<{ label: string, value: any }>
+ * - onValueChange : (val) => void
+ * - placeholder : string – Text when no value selected
+ * - disabled : boolean
+ */
+const SelectInput = ({
+ label,
+ value,
+ options = [],
+ onValueChange,
+ placeholder = 'Select',
+ disabled = false,
+}) => {
+ const [modalVisible, setModalVisible] = useState(false);
+
+ const selectedLabel = () => {
+ const found = options.find((o) => o.value === value);
+ return found ? found.label : placeholder;
+ };
+
+ const openModal = () => {
+ if (disabled) return;
+ setModalVisible(true);
+ };
+
+ const closeModal = () => setModalVisible(false);
+
+ const handleSelect = (val) => {
+ onValueChange && onValueChange(val);
+ closeModal();
+ };
+
+ return (
+
+ {label && {label}}
+
+
+ {selectedLabel()}
+
+
+
+
+
+
+
+
+
+ String(item.value)}
+ renderItem={({ item }) => (
+ handleSelect(item.value)}
+ >
+ {item.label}
+
+ )}
+ />
+
+
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ marginBottom: 20,
+ },
+ label: {
+ fontSize: 14,
+ fontWeight: '600',
+ color: COLORS.textPrimary,
+ marginBottom: 8,
+ },
+ selectBox: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ borderWidth: 1,
+ borderColor: COLORS.gray[300],
+ borderRadius: 12,
+ backgroundColor: COLORS.white,
+ paddingHorizontal: 16,
+ minHeight: 52,
+ },
+ selectText: {
+ fontSize: 16,
+ color: COLORS.textPrimary,
+ flex: 1,
+ marginRight: 8,
+ },
+ disabled: {
+ backgroundColor: COLORS.gray[100],
+ opacity: 0.6,
+ },
+ modalOverlay: {
+ flex: 1,
+ backgroundColor: 'rgba(0,0,0,0.3)',
+ justifyContent: 'center',
+ paddingHorizontal: 24,
+ },
+ modalContainer: {
+ backgroundColor: COLORS.white,
+ borderRadius: 12,
+ maxHeight: '70%',
+ },
+ optionRow: {
+ paddingVertical: 16,
+ paddingHorizontal: 20,
+ borderBottomWidth: 1,
+ borderBottomColor: COLORS.gray[200],
+ },
+ optionText: {
+ fontSize: 16,
+ color: COLORS.textPrimary,
+ },
+});
+
+export default SelectInput;
\ No newline at end of file
diff --git a/src/screens/Loan/CreateLoanPaidOffLetterOrderScreen.js b/src/screens/Loan/CreateLoanPaidOffLetterOrderScreen.js
index 9557add..92777ff 100644
--- a/src/screens/Loan/CreateLoanPaidOffLetterOrderScreen.js
+++ b/src/screens/Loan/CreateLoanPaidOffLetterOrderScreen.js
@@ -1,11 +1,15 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } 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 SelectInput from '../../components/SelectInput';
+import DateInput from '../../components/DateInput';
+import { API_CONFIG } from '../../constants/api';
+import { useAuth } from '../../contexts/AuthContext';
import apiService from '../../services/apiService';
+import { StatusBar } from 'expo-status-bar';
const CreateLoanPaidOffLetterOrderScreen = () => {
const navigation = useNavigation();
@@ -25,6 +29,57 @@ const CreateLoanPaidOffLetterOrderScreen = () => {
const [loanReason, setLoanReason] = useState('');
const [loading, setLoading] = useState(false);
+ // Options
+ const [regionOptions, setRegionOptions] = useState([]);
+ const [passportSeriesOptions, setPassportSeriesOptions] = useState([]);
+ const [branchesByRegion, setBranchesByRegion] = useState({});
+
+ const { user } = useAuth();
+
+ useEffect(() => {
+ // Prefill user passport if available
+ if (user) {
+ if (user.passport_serie) setPassportSerie(user.passport_serie);
+ if (user.passport_id) setPassportId(String(user.passport_id));
+ if (user.phone) setPhone(String(user.phone).slice(-8));
+ if (user.region) setRegion(user.region);
+ }
+
+ const fetchEnums = async () => {
+ try {
+ const res = await fetch(`${API_CONFIG.BASE_URL}/base-app-enums`);
+ const enums = await res.json();
+
+ // Regions
+ const regions = Object.entries(enums.regions || {}).map(([value, label]) => ({ value, label }));
+ setRegionOptions(regions);
+
+ // Passport series
+ const pSeries = Object.keys(enums.passport_series || {}).map((key) => ({ value: key, label: key }));
+ setPassportSeriesOptions(pSeries);
+ } catch (e) {
+ console.warn('Failed loading enums', e.message);
+ }
+ };
+
+ const fetchBranches = async () => {
+ try {
+ const res = await fetch(`${API_CONFIG.BASE_URL}/branches?groupBy=region`);
+ const json = await res.json();
+ setBranchesByRegion(json);
+ } catch (e) {
+ console.warn('Failed loading branches', e.message);
+ }
+ };
+
+ fetchEnums();
+ fetchBranches();
+ }, []);
+
+ const branchOptions = region && branchesByRegion[region]
+ ? branchesByRegion[region].map((b) => ({ label: b.name, value: b.id }))
+ : [];
+
const handleSubmit = async () => {
// Basic validation – ensure all required fields are filled
if (
@@ -51,7 +106,7 @@ const CreateLoanPaidOffLetterOrderScreen = () => {
customer_name: customerName,
customer_surname: customerSurname,
passport_serie: passportSerie,
- passport_id: parseInt(passportId),
+ passport_id: passportId.trim(),
born_at: bornAt,
phone: parseInt(phone),
loan_contract_number: contractNumber,
@@ -85,23 +140,27 @@ const CreateLoanPaidOffLetterOrderScreen = () => {
-
+
Täze güwanama sargyt et
{/* Region & Branch */}
- {
+ setRegion(val);
+ setBranchId('');
+ }}
+ placeholder="Saýla"
/>
- setBranchId(val)}
+ placeholder="Saýla"
+ disabled={branchOptions.length === 0}
/>
{/* Customer */}
@@ -119,12 +178,12 @@ const CreateLoanPaidOffLetterOrderScreen = () => {
/>
{/* Passport */}
-
{
/>
{/* Other personal */}
-
{
value={contractNumber}
onChangeText={setContractNumber}
/>
-