loan paid off working
This commit is contained in:
152
src/components/DateInput.js
Normal file
152
src/components/DateInput.js
Normal file
@@ -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 (
|
||||
<View style={styles.container}>
|
||||
{label && <Text style={styles.label}>{label}</Text>}
|
||||
<TouchableOpacity style={styles.selectBox} onPress={open} activeOpacity={0.7}>
|
||||
<Text style={[styles.selectText, !value && { color: COLORS.gray[400] }]}>{value || placeholder}</Text>
|
||||
<Ionicons name="calendar" size={18} color={COLORS.gray[400]} />
|
||||
</TouchableOpacity>
|
||||
|
||||
{show && Platform.OS === 'android' && (
|
||||
<DateTimePicker
|
||||
value={tempDate}
|
||||
mode="date"
|
||||
display="calendar"
|
||||
onChange={onChangeAndroid}
|
||||
maximumDate={maximumDate}
|
||||
minimumDate={minimumDate}
|
||||
/>
|
||||
)}
|
||||
|
||||
{Platform.OS === 'ios' && (
|
||||
<Modal visible={show} transparent animationType="fade">
|
||||
<View style={styles.modalOverlay}>
|
||||
<View style={styles.modalContent}>
|
||||
<DateTimePicker
|
||||
value={tempDate}
|
||||
mode="date"
|
||||
display="spinner"
|
||||
onChange={(e, d) => d && setTempDate(d)}
|
||||
maximumDate={maximumDate}
|
||||
minimumDate={minimumDate}
|
||||
textColor={COLORS.textPrimary}
|
||||
themeVariant="light"
|
||||
style={{ width: '100%', backgroundColor: COLORS.white, height: 220 }}
|
||||
/>
|
||||
<View style={styles.modalButtons}>
|
||||
<TouchableOpacity style={styles.modalBtn} onPress={close}>
|
||||
<Text style={styles.modalBtnTextCancel}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={styles.modalBtn} onPress={onConfirmIOS}>
|
||||
<Text style={styles.modalBtnText}>Confirm</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
154
src/components/SelectInput.js
Normal file
154
src/components/SelectInput.js
Normal file
@@ -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 (
|
||||
<View style={styles.container}>
|
||||
{label && <Text style={styles.label}>{label}</Text>}
|
||||
<TouchableOpacity
|
||||
style={[styles.selectBox, disabled && styles.disabled]}
|
||||
onPress={openModal}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text
|
||||
style={[styles.selectText, !value && { color: COLORS.gray[400] }]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{selectedLabel()}
|
||||
</Text>
|
||||
<Ionicons name="chevron-down" size={18} color={COLORS.gray[400]} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<Modal
|
||||
animationType="slide"
|
||||
transparent
|
||||
visible={modalVisible}
|
||||
onRequestClose={closeModal}
|
||||
>
|
||||
<TouchableWithoutFeedback onPress={closeModal}>
|
||||
<View style={styles.modalOverlay}>
|
||||
<TouchableWithoutFeedback>
|
||||
<View style={styles.modalContainer}>
|
||||
<FlatList
|
||||
data={options}
|
||||
keyExtractor={(item) => String(item.value)}
|
||||
renderItem={({ item }) => (
|
||||
<TouchableOpacity
|
||||
style={styles.optionRow}
|
||||
onPress={() => handleSelect(item.value)}
|
||||
>
|
||||
<Text style={styles.optionText}>{item.label}</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</Modal>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
Reference in New Issue
Block a user