From a520a9065f800050b4377299dd6140384e283e6d Mon Sep 17 00:00:00 2001 From: Nurmuhammet Allanov Date: Tue, 8 Jul 2025 16:06:22 +0500 Subject: [PATCH] loan order details more beautiful --- .cursor/rules/api.mdc | 6 + CHANGELOG.md | 0 resources/openapi.json | 683 +++++++----------- roadmap.md | 0 src/navigation/MenuNavigator.js | 6 + .../Card/CardTransactionOrderDetailsScreen.js | 253 +++++++ .../Card/CardTransactionOrdersScreen.js | 190 +++++ .../Card/CreateCardTransactionOrderScreen.js | 150 ++++ src/screens/Loan/LoanOrderDetailsScreen.js | 155 +++- src/screens/Main/MenuScreen.js | 2 + src/services/apiService.js | 58 ++ src/services/authService.js | 69 ++ 12 files changed, 1126 insertions(+), 446 deletions(-) create mode 100644 .cursor/rules/api.mdc create mode 100644 CHANGELOG.md create mode 100644 roadmap.md create mode 100644 src/screens/Card/CardTransactionOrderDetailsScreen.js create mode 100644 src/screens/Card/CardTransactionOrdersScreen.js create mode 100644 src/screens/Card/CreateCardTransactionOrderScreen.js diff --git a/.cursor/rules/api.mdc b/.cursor/rules/api.mdc new file mode 100644 index 0000000..8a16838 --- /dev/null +++ b/.cursor/rules/api.mdc @@ -0,0 +1,6 @@ +--- +description: +globs: +alwaysApply: false +--- +All api are located inside resources openapi.json \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/resources/openapi.json b/resources/openapi.json index c40a229..1c939a6 100644 --- a/resources/openapi.json +++ b/resources/openapi.json @@ -278,6 +278,30 @@ } } }, + "/alerts-all": { + "get": { + "operationId": "alert.all", + "summary": "All alerts", + "tags": [ + "Alert" + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + } + } + } + }, "/auth/register": { "post": { "operationId": "apiAuth.register", @@ -387,12 +411,24 @@ "schema": { "type": "object", "properties": { + "success": { + "type": "boolean" + }, + "token": { + "type": "string" + }, "message": { "type": "string" + }, + "user": { + "$ref": "#/components/schemas/ProfileResponse" } }, "required": [ - "message" + "success", + "token", + "message", + "user" ] } } @@ -479,6 +515,69 @@ } } }, + "/contact-us": { + "post": { + "operationId": "contactUs.store", + "summary": "Store contact us message", + "tags": [ + "ContactUs" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Message Title", + "example": "Salam", + "maxLength": 255 + }, + "message": { + "type": "string", + "description": "Message content", + "example": "Bet app", + "maxLength": 255 + } + }, + "required": [ + "title", + "message" + ] + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + } + } + } + }, "/metrics": { "get": { "operationId": "metrics.index", @@ -528,31 +627,11 @@ ], "responses": { "200": { - "description": "", + "description": "`ProfileResponse`", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "passport_serie": { - "type": "string" - }, - "passport_id": { - "type": "string" - } - }, - "required": [ - "name", - "phone", - "passport_serie", - "passport_id" - ] + "$ref": "#/components/schemas/ProfileResponse" } } } @@ -2582,14 +2661,11 @@ ], "responses": { "200": { - "description": "Paginated set of `LoanOrderIndexResource`", + "description": "", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/LoanOrderIndexResource" - } + "type": "string" } } } @@ -3474,6 +3550,22 @@ } } } + }, + "/visa-master-order": { + "get": { + "operationId": "visaMasterPaymentOrder.index", + "tags": [ + "VisaMasterPaymentOrder" + ], + "responses": { + "200": { + "description": "" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + } + } + } } }, "components": { @@ -5155,391 +5247,6 @@ ], "title": "CardTransaction" }, - "LoanOrderIndexResource": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "unique_id": { - "type": [ - "string", - "null" - ] - }, - "loan_type": { - "type": "integer" - }, - "region": { - "type": "string" - }, - "branch_id": { - "type": "integer" - }, - "customer_name": { - "type": "string" - }, - "customer_surname": { - "type": "string" - }, - "customer_patronic_name": { - "type": [ - "string", - "null" - ] - }, - "passport_address": { - "type": "string" - }, - "real_address": { - "type": "string" - }, - "passport_serie": { - "type": "string" - }, - "passport_id": { - "type": "string" - }, - "passport_given_at": { - "type": "string", - "format": "date-time" - }, - "passport_given_by": { - "type": "string" - }, - "born_place": { - "type": "string" - }, - "born_at": { - "type": "string", - "format": "date-time" - }, - "email": { - "type": [ - "string", - "null" - ] - }, - "phone": { - "type": "string" - }, - "phone_additional": { - "type": [ - "string", - "null" - ] - }, - "phone_home": { - "type": [ - "string", - "null" - ] - }, - "work_region": { - "type": [ - "string", - "null" - ] - }, - "work_province_id": { - "type": [ - "integer", - "null" - ] - }, - "work_company": { - "type": [ - "string", - "null" - ] - }, - "work_company_accountant_number": { - "type": [ - "string", - "null" - ] - }, - "work_started_at": { - "type": [ - "string", - "null" - ], - "format": "date-time" - }, - "work_salary": { - "type": [ - "string", - "null" - ] - }, - "work_position": { - "type": [ - "string", - "null" - ] - }, - "education": { - "type": "string" - }, - "marriage_status": { - "type": "string" - }, - "passport_one": { - "type": "string" - }, - "passport_two": { - "type": "string" - }, - "passport_three": { - "type": "string" - }, - "passport_four": { - "type": "string" - }, - "user_id": { - "type": [ - "integer", - "null" - ] - }, - "status": { - "type": [ - "string", - "null" - ] - }, - "notes": { - "type": [ - "string", - "null" - ] - }, - "created_at": { - "type": [ - "string", - "null" - ], - "format": "date-time" - }, - "updated_at": { - "type": [ - "string", - "null" - ], - "format": "date-time" - }, - "deleted_at": { - "type": [ - "string", - "null" - ], - "format": "date-time" - }, - "loan_amount": { - "type": [ - "string", - "null" - ] - }, - "guarantor_name": { - "type": [ - "string", - "null" - ] - }, - "guarantor_surname": { - "type": [ - "string", - "null" - ] - }, - "guarantor_patronic_name": { - "type": [ - "string", - "null" - ] - }, - "guarantor_card_number": { - "type": [ - "string", - "null" - ] - }, - "guarantor_card_name": { - "type": [ - "string", - "null" - ] - }, - "guarantor_card_month": { - "type": [ - "string", - "null" - ] - }, - "guarantor_card_year": { - "type": [ - "string", - "null" - ] - }, - "guarantor_2_name": { - "type": [ - "string", - "null" - ] - }, - "guarantor_2_surname": { - "type": [ - "string", - "null" - ] - }, - "guarantor_2_patronic_name": { - "type": [ - "string", - "null" - ] - }, - "guarantor_2_card_number": { - "type": [ - "string", - "null" - ] - }, - "guarantor_2_card_name": { - "type": [ - "string", - "null" - ] - }, - "guarantor_2_card_month": { - "type": [ - "string", - "null" - ] - }, - "guarantor_2_card_year": { - "type": [ - "string", - "null" - ] - }, - "source": { - "type": "string", - "enum": [ - "mobile" - ] - }, - "guarantor_note": { - "type": [ - "string", - "null" - ] - }, - "guarantor_2_note": { - "type": [ - "string", - "null" - ] - }, - "satisfiable": { - "type": [ - "string", - "null" - ] - }, - "guarantor_passport_serie": { - "type": [ - "string", - "null" - ] - }, - "guarantor_passport_id": { - "type": [ - "string", - "null" - ] - }, - "guarantor_2_passport_serie": { - "type": [ - "string", - "null" - ] - }, - "guarantor_2_passport_id": { - "type": [ - "string", - "null" - ] - } - }, - "required": [ - "id", - "unique_id", - "loan_type", - "region", - "branch_id", - "customer_name", - "customer_surname", - "customer_patronic_name", - "passport_address", - "real_address", - "passport_serie", - "passport_id", - "passport_given_at", - "passport_given_by", - "born_place", - "born_at", - "email", - "phone", - "phone_additional", - "phone_home", - "work_region", - "work_province_id", - "work_company", - "work_company_accountant_number", - "work_started_at", - "work_salary", - "work_position", - "education", - "marriage_status", - "passport_one", - "passport_two", - "passport_three", - "passport_four", - "user_id", - "status", - "notes", - "created_at", - "updated_at", - "deleted_at", - "loan_amount", - "guarantor_name", - "guarantor_surname", - "guarantor_patronic_name", - "guarantor_card_number", - "guarantor_card_name", - "guarantor_card_month", - "guarantor_card_year", - "guarantor_2_name", - "guarantor_2_surname", - "guarantor_2_patronic_name", - "guarantor_2_card_number", - "guarantor_2_card_name", - "guarantor_2_card_month", - "guarantor_2_card_year", - "source", - "guarantor_note", - "guarantor_2_note", - "satisfiable", - "guarantor_passport_serie", - "guarantor_passport_id", - "guarantor_2_passport_serie", - "guarantor_2_passport_id" - ], - "title": "LoanOrderIndexResource" - }, "LoanOrderShowResource": { "type": "object", "properties": { @@ -6196,7 +5903,10 @@ "maxLength": 255 }, "guarantor_patronic_name": { - "type": "string", + "type": [ + "string", + "null" + ], "description": "Guarantor surname", "example": "Owezowic", "maxLength": 255 @@ -6346,7 +6056,6 @@ "passport_four", "guarantor_name", "guarantor_surname", - "guarantor_patronic_name", "guarantor_card_number", "guarantor_card_name", "guarantor_card_month", @@ -7060,6 +6769,45 @@ ], "title": "LoanRemainingOrder" }, + "ProfileResponse": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "phone": { + "type": [ + "string", + "null" + ] + }, + "passport_serie": { + "type": "string" + }, + "passport_id": { + "type": "string" + }, + "card_number": { + "type": "string" + }, + "card_month": { + "type": "string" + }, + "card_year": { + "type": "string" + } + }, + "required": [ + "name", + "phone", + "passport_serie", + "passport_id", + "card_number", + "card_month", + "card_year" + ], + "title": "ProfileResponse" + }, "UpdateUserProfileRequest": { "type": "object", "properties": { @@ -7083,10 +6831,7 @@ "example": "MyFcpassword" }, "passport_serie": { - "type": [ - "string", - "null" - ], + "type": "string", "description": "Passport serie", "enum": [ "I-AS", @@ -7104,12 +6849,104 @@ "example": "I-AS" }, "passport_id": { - "type": [ - "number", - "null" - ], + "type": "number", "description": "Passport id", "example": 100999 + }, + "card_number": { + "type": "string", + "example": "9934612100000543" + }, + "card_month": { + "type": "string", + "enum": [ + "01", + "02", + "03", + "04", + "05", + "06", + "07", + "08", + "09", + "10", + "11", + "12" + ], + "example": "12" + }, + "card_year": { + "type": "string", + "enum": [ + "2024", + "2025", + "2026", + "2027", + "2028", + "2029", + "2030", + "2031", + "2032", + "2033", + "2034", + "2035", + "2036", + "2037", + "2038", + "2039", + "2040", + "2041", + "2042", + "2043", + "2044", + "2045", + "2046", + "2047", + "2048", + "2049", + "2050", + "2051", + "2052", + "2053", + "2054", + "2055", + "2056", + "2057", + "2058", + "2059", + "2060", + "2061", + "2062", + "2063", + "2064", + "2065", + "2066", + "2067", + "2068", + "2069", + "2070", + "2071", + "2072", + "2073", + "2074", + "2075", + "2076", + "2077", + "2078", + "2079", + "2080", + "2081", + "2082", + "2083", + "2084", + "2085", + "2086", + "2087", + "2088", + "2089", + "2090" + ], + "example": "2049" } }, "required": [ diff --git a/roadmap.md b/roadmap.md new file mode 100644 index 0000000..e69de29 diff --git a/src/navigation/MenuNavigator.js b/src/navigation/MenuNavigator.js index 8fd67cd..83e78e7 100644 --- a/src/navigation/MenuNavigator.js +++ b/src/navigation/MenuNavigator.js @@ -9,6 +9,9 @@ import LoanPaidOffLetterOrderDetailsScreen from '../screens/Loan/LoanPaidOffLett import LoanOrdersScreen from '../screens/Loan/LoanOrdersScreen'; import CreateLoanOrderScreen from '../screens/Loan/CreateLoanOrderScreen'; import LoanOrderDetailsScreen from '../screens/Loan/LoanOrderDetailsScreen'; +import CardTransactionOrdersScreen from '../screens/Card/CardTransactionOrdersScreen'; +import CreateCardTransactionOrderScreen from '../screens/Card/CreateCardTransactionOrderScreen'; +import CardTransactionOrderDetailsScreen from '../screens/Card/CardTransactionOrderDetailsScreen'; const Stack = createStackNavigator(); @@ -23,6 +26,9 @@ const MenuNavigator = () => ( + + + ); diff --git a/src/screens/Card/CardTransactionOrderDetailsScreen.js b/src/screens/Card/CardTransactionOrderDetailsScreen.js new file mode 100644 index 0000000..f240df2 --- /dev/null +++ b/src/screens/Card/CardTransactionOrderDetailsScreen.js @@ -0,0 +1,253 @@ +import React, { useEffect, useState } from 'react'; +import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, ScrollView, SafeAreaView, Modal } 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'; +import DateInput from '../../components/DateInput'; +import { Linking } from 'react-native'; + +const DetailRow = ({ label, value, showBorder = true }) => ( + + {label} + {String(value)} + +); + +const CardTransactionOrderDetailsScreen = () => { + const navigation = useNavigation(); + const route = useRoute(); + const { orderId } = route.params || {}; + + const [loading, setLoading] = useState(true); + const [order, setOrder] = useState(null); + const [modalVisible, setModalVisible] = useState(false); + const [startDate, setStartDate] = useState(''); + const [endDate, setEndDate] = useState(''); + const [downloading, setDownloading] = useState(false); + + const fetchDetails = async () => { + setLoading(true); + const res = await apiService.getCardTransactionOrder(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.deleteCardTransactionOrder(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'); + } + }; + + const openDownloadModal = () => { + setStartDate(''); + setEndDate(''); + setModalVisible(true); + }; + + const handleDownload = async () => { + if (!startDate || !endDate) { + Alert.alert('Error', 'Select both dates'); + return; + } + setDownloading(true); + const res = await apiService.downloadCardTransactions(orderId, startDate, endDate); + setDownloading(false); + if (res.success && res.data?.url) { + Linking.openURL(res.data.url); + setModalVisible(false); + } else { + Alert.alert('Error', res.error || 'Could not download'); + } + }; + + if (loading) { + return ( + + + + ); + } + + if (!order) { + return ( + + No data + + ); + } + + return ( + + + navigation.goBack()}> + + + + + Kart hereketleri + + + + {order.card_number && } + {order.card_month && order.card_year && } + {order.passport_serie && order.passport_id && } + + + + Ýükle (PDF) + + + + Poz + + + + {/* Download modal */} + setModalVisible(false)}> + + + Sene aralygy + + + + + {downloading ? ( + + ) : ( + Ýükle + )} + + + setModalVisible(false)} style={{ marginTop: 16, alignItems: 'center' }}> + Ýap + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: COLORS.backgroundSecondary, + paddingTop: 40, + }, + centered: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + backBtn: { + alignSelf: 'flex-end', + marginRight: 24, + 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', + paddingVertical: 12, + }, + detailRowBorder: { + borderBottomWidth: 1, + borderBottomColor: COLORS.border, + }, + detailKey: { + fontWeight: '600', + color: COLORS.textSecondary, + }, + detailValue: { + color: COLORS.textPrimary, + }, + actionBtn: { + backgroundColor: COLORS.primary, + paddingVertical: 16, + borderRadius: 8, + alignItems: 'center', + marginBottom: 16, + }, + actionText: { + color: COLORS.white, + fontSize: 16, + fontWeight: '600', + }, + deleteBtn: { + backgroundColor: COLORS.error, + paddingVertical: 16, + borderRadius: 8, + alignItems: 'center', + }, + deleteText: { + color: COLORS.white, + fontSize: 16, + fontWeight: '600', + }, + modalBackdrop: { + flex: 1, + backgroundColor: 'rgba(0,0,0,0.25)', + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: 24, + }, + modalCard: { + backgroundColor: COLORS.white, + borderRadius: 12, + width: '100%', + padding: 24, + }, + modalTitle: { + fontSize: 18, + fontWeight: 'bold', + color: COLORS.textPrimary, + marginBottom: 16, + }, + submitBtn: { + marginTop: 8, + backgroundColor: COLORS.primary, + paddingVertical: 14, + borderRadius: 8, + alignItems: 'center', + }, + submitText: { + color: COLORS.white, + fontSize: 16, + fontWeight: '600', + }, +}); + +export default CardTransactionOrderDetailsScreen; \ No newline at end of file diff --git a/src/screens/Card/CardTransactionOrdersScreen.js b/src/screens/Card/CardTransactionOrdersScreen.js new file mode 100644 index 0000000..56e9523 --- /dev/null +++ b/src/screens/Card/CardTransactionOrdersScreen.js @@ -0,0 +1,190 @@ +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 CardTransactionOrdersScreen = () => { + 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.getCardTransactionOrders(); + 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 = (item) => { + navigation.navigate('CardTransactionOrderDetails', { orderId: item.id }); + }; + + const renderItem = ({ item }) => { + const cardNum = item.card_number || ''; + const masked = cardNum ? `${cardNum.slice(0, 6)}******${cardNum.slice(-4)}` : ''; + const created = item.created_at ? new Date(item.created_at).toLocaleDateString() : ''; + const passportLine = item.passport_serie && item.passport_id ? `Pasport: ${item.passport_serie} ${item.passport_id}` : ''; + + return ( + handleItemPress(item)}> + + {item.id} + + + {masked} + {passportLine !== '' && {passportLine}} + {created} + + + ); + }; + + if (loading) { + return ( + + + + ); + } + + return ( + + + {/* Header */} + + navigation.goBack()} style={{ paddingRight: 12 }}> + + + Kart hereketleri + + item.id?.toString()} + renderItem={renderItem} + contentContainerStyle={orders.length === 0 && styles.emptyContainer} + refreshControl={} + ListEmptyComponent={No orders yet} + /> + + {/* Floating Action Button */} + navigation.navigate('CreateCardTransactionOrder')} + > + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: COLORS.backgroundSecondary, + }, + centered: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + header: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 24, + paddingVertical: 16, + }, + headerTitle: { + fontSize: 20, + fontWeight: 'bold', + color: COLORS.textPrimary, + }, + 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, + }, + cardNumber: { + fontWeight: '700', + 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', + bottom: 32, + right: 32, + backgroundColor: COLORS.primary, + width: 56, + height: 56, + borderRadius: 28, + alignItems: 'center', + justifyContent: 'center', + elevation: 4, + }, + passportText: { + color: COLORS.textSecondary, + fontSize: 14, + marginBottom: 2, + }, +}); + +export default CardTransactionOrdersScreen; \ No newline at end of file diff --git a/src/screens/Card/CreateCardTransactionOrderScreen.js b/src/screens/Card/CreateCardTransactionOrderScreen.js new file mode 100644 index 0000000..5a437d9 --- /dev/null +++ b/src/screens/Card/CreateCardTransactionOrderScreen.js @@ -0,0 +1,150 @@ +import React, { useState, useEffect } from 'react'; +import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, SafeAreaView, ScrollView } 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 SelectInput from '../../components/SelectInput'; +import { StatusBar } from 'expo-status-bar'; +import { useAuth } from '../../contexts/AuthContext'; + +// Build options for months and years +const monthOptions = Array.from({ length: 12 }).map((_, i) => { + const m = String(i + 1).padStart(2, '0'); + return { label: m, value: m }; +}); +const yearOptions = Array.from({ length: 60 }).map((_, i) => { + const y = String(new Date().getFullYear() + i); + return { label: y, value: y }; +}); + +const PASSPORT_SERIES = ['I-AS','I-MR','II-MR','I-AH','II-AH','I-LB','II-LB','I-BN','II-BN','I-DZ','II-DZ']; + +const CreateCardTransactionOrderScreen = () => { + const navigation = useNavigation(); + const [cardNumber, setCardNumber] = useState(''); + const [cardMonth, setCardMonth] = useState(''); + const [cardYear, setCardYear] = useState(''); + const [loading, setLoading] = useState(false); + const { user } = useAuth(); + const [passportSerie, setPassportSerie] = useState(''); + const [passportId, setPassportId] = useState(''); + + useEffect(() => { + if (user) { + if (user.passport_serie) setPassportSerie(user.passport_serie); + if (user.passport_id) setPassportId(String(user.passport_id)); + } + }, [user]); + + const handleSubmit = async () => { + if (!cardNumber.trim() || !cardMonth || !cardYear || !passportSerie || !passportId.trim()) { + Alert.alert('Error', 'All fields are required'); + return; + } + setLoading(true); + const res = await apiService.createCardTransactionOrder(cardNumber.trim(), cardMonth, cardYear, passportSerie, passportId.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 ( + + + + navigation.goBack()}> + + + + Täze sargyt + + ({ label: v, value: v }))} + placeholder="Saýla" + /> + + + + + + + + + + + {loading ? ( + + ) : ( + Ýatda sakla + )} + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: COLORS.backgroundSecondary, + }, + 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 CreateCardTransactionOrderScreen; \ No newline at end of file diff --git a/src/screens/Loan/LoanOrderDetailsScreen.js b/src/screens/Loan/LoanOrderDetailsScreen.js index 4654df0..cb72455 100644 --- a/src/screens/Loan/LoanOrderDetailsScreen.js +++ b/src/screens/Loan/LoanOrderDetailsScreen.js @@ -6,14 +6,30 @@ import apiService from '../../services/apiService'; import { COLORS } from '../../constants/colors'; import { StatusBar } from 'expo-status-bar'; -const formatDate = (dstr) => { - if (!dstr) return '-'; - const d = new Date(dstr); - return isNaN(d) ? dstr : d.toLocaleDateString('tk-TM'); +// Helper formatters +const formatDate = (dateStr) => { + if (!dateStr) return ''; + const d = new Date(dateStr); + if (isNaN(d)) return dateStr; + return d.toLocaleDateString('tk-TM'); }; -const DetailRow = ({ label, value, last }) => ( - +const formatDateTime = (dateStr) => { + if (!dateStr) return ''; + const d = new Date(dateStr); + if (isNaN(d)) return dateStr; + return d.toLocaleString('tk-TM', { + hour: '2-digit', + minute: '2-digit', + day: '2-digit', + month: '2-digit', + year: 'numeric', + }); +}; + +// Key/value row +const DetailRow = ({ label, value, showBorder = true }) => ( + {label} {String(value)} @@ -76,6 +92,11 @@ const LoanOrderDetailsScreen = () => { ); } + // Pre-computed helpers + const fullName = `${order.customer_name ?? ''} ${order.customer_surname ?? ''} ${order.customer_patronic_name ?? ''}`.trim(); + const guarantorFullName = `${order.guarantor_name ?? ''} ${order.guarantor_surname ?? ''} ${order.guarantor_patronic_name ?? ''}`.trim(); + const guarantor2FullName = `${order.guarantor_2_name ?? ''} ${order.guarantor_2_surname ?? ''} ${order.guarantor_2_patronic_name ?? ''}`.trim(); + return ( @@ -83,32 +104,115 @@ const LoanOrderDetailsScreen = () => { - - Karz sargyt maglumatlary + {/* Ensure enough bottom padding so content is not hidden behind the tab bar */} + + Karz sargyt: {order.unique_id} + {/* Basic order info */} - - - - - + {order.unique_id && } + {order.created_at && } + {order.updated_at && } + {order.status && } + - Müşderi + {/* Loan details */} + Karz barada maglumatlar - - - - + {order.loan_amount && } + {order.loan_type && } + {order.satisfiable && } - {/* Address */} - Salgylary + {/* Location */} + {(order.region || order.branch) && ( + <> + Lokasiýa + + {order.region && } + {order.branch && } + + + )} + + {/* Personal information */} + Şahsy maglumatlar - - + + {order.born_at && } + {order.born_place && } + {order.marriage_status && } + {order.education && } + {order.phone && } + {order.phone_additional && } + {order.phone_home && } + {order.email && } + {order.passport_serie && order.passport_id && } + {order.passport_given_at && } + {order.passport_given_by && } + {order.passport_address && } + {/* Work info */} + {(order.work_company || order.work_position) && ( + <> + Iş barada maglumatlar + + {order.work_company && } + {order.work_position && } + {order.work_salary && } + {order.work_started_at && } + {order.work_company_accountant_number && } + {order.work_region && } + {order.work_province && } + + + )} + + {/* Applicant card info */} + {order.card_number && ( + <> + Kartanyň maglumatlary (Arza beriji) + + {order.card_name && } + + {order.card_month && } + {order.card_year && } + + + )} + + {/* Guarantor 1 */} + {guarantorFullName.trim() && ( + <> + Zamun (1) + + + {order.guarantor_card_name && } + {order.guarantor_card_number && } + {order.guarantor_card_month && } + {order.guarantor_card_year && } + {order.guarantor_note && } + + + )} + + {/* Guarantor 2 */} + {guarantor2FullName.trim() && ( + <> + Zamun (2) + + + {order.guarantor_2_card_name && } + {order.guarantor_2_card_number && } + {order.guarantor_2_card_month && } + {order.guarantor_2_card_year && } + {order.guarantor_2_note && } + + + )} + Poz @@ -118,7 +222,12 @@ const LoanOrderDetailsScreen = () => { }; const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: COLORS.backgroundSecondary }, + container: { + flex: 1, + backgroundColor: COLORS.backgroundSecondary, + paddingHorizontal: 24, + paddingTop: 40, + }, centered: { flex: 1, alignItems: 'center', justifyContent: 'center' }, backBtn: { alignSelf: 'flex-end', marginBottom: 16, marginRight: 24 }, title: { fontSize: 24, fontWeight: 'bold', color: COLORS.textPrimary, marginBottom: 24 }, diff --git a/src/screens/Main/MenuScreen.js b/src/screens/Main/MenuScreen.js index be8215e..959e5eb 100644 --- a/src/screens/Main/MenuScreen.js +++ b/src/screens/Main/MenuScreen.js @@ -50,6 +50,8 @@ const MenuScreen = () => { navigation.navigate('LoanRemainingOrders'); } else if (item.id === 3) { navigation.navigate('LoanPaidOffLetterOrders'); + } else if (item.id === 5) { + navigation.navigate('CardTransactionOrders'); } else { console.log('Menu item pressed:', item.title); } diff --git a/src/services/apiService.js b/src/services/apiService.js index f0ac419..36b3667 100644 --- a/src/services/apiService.js +++ b/src/services/apiService.js @@ -214,6 +214,64 @@ class ApiService { } } + // ================================ + // Card Transactions Orders (Kart hereketleri) + // ================================ + + async getCardTransactionOrders() { + try { + const data = await authService.getCardTransactionOrders(); + return { success: true, data }; + } catch (error) { + return { success: false, error: error.message }; + } + } + + async createCardTransactionOrder(cardNumber, cardMonth, cardYear, passportSerie = null, passportId = null) { + try { + const response = await authService.createCardTransactionOrder(cardNumber, cardMonth, cardYear, passportSerie, passportId); + return { success: true, message: response.message }; + } catch (error) { + return { success: false, error: error.message }; + } + } + + async getCardTransactionOrder(orderId) { + try { + const data = await authService.getCardTransactionOrder(orderId); + return { success: true, data }; + } catch (error) { + return { success: false, error: error.message }; + } + } + + async updateCardTransactionOrder(orderId, cardNumber, cardMonth, cardYear, passportSerie = null, passportId = null) { + try { + const response = await authService.updateCardTransactionOrder(orderId, cardNumber, cardMonth, cardYear, passportSerie, passportId); + return { success: true, message: response.message }; + } catch (error) { + return { success: false, error: error.message }; + } + } + + async deleteCardTransactionOrder(orderId) { + try { + const response = await authService.deleteCardTransactionOrder(orderId); + return { success: true, message: response.message }; + } catch (error) { + return { success: false, error: error.message }; + } + } + + async downloadCardTransactions(orderId, startDate, endDate) { + try { + const response = await authService.downloadCardTransactions(orderId, startDate, endDate); + return { success: true, data: response }; + } catch (error) { + return { success: false, error: error.message }; + } + } + // Utility methods for common operations async getUserDashboardData() { try { diff --git a/src/services/authService.js b/src/services/authService.js index 7418cd4..d04fc5e 100644 --- a/src/services/authService.js +++ b/src/services/authService.js @@ -301,6 +301,75 @@ class AuthService { async deleteLoanOrder(orderId) { return this.makeRequest(`/loan-order/${orderId}`, null, true, 'DELETE'); } + + // ================================ + // Card Transactions Orders (Kart hereketleri) + // ================================ + + // LIST + async getCardTransactionOrders() { + return this.makeRequest('/card-transactions', null, true, 'GET'); + } + + // CREATE order (auto-fills passport data from stored profile) + async createCardTransactionOrder(cardNumber, cardMonth, cardYear, passportSerie = null, passportId = null) { + let serie = passportSerie; + let pid = passportId; + if (!serie || !pid) { + const user = await this.getStoredUser(); + serie = serie || user?.passport_serie; + pid = pid || user?.passport_id; + } + if (!serie || !pid) { + throw new Error('Passport details are missing'); + } + const payload = { + passport_serie: serie, + passport_id: pid, + card_number: cardNumber, + card_month: cardMonth, + card_year: cardYear, + }; + return this.makeRequest('/card-transactions', payload, true, 'POST'); + } + + // SHOW + async getCardTransactionOrder(orderId) { + return this.makeRequest(`/card-transactions/${orderId}`, null, true, 'GET'); + } + + // UPDATE + async updateCardTransactionOrder(orderId, cardNumber, cardMonth, cardYear, passportSerie = null, passportId = null) { + let serie = passportSerie; + let pid = passportId; + if (!serie || !pid) { + const user = await this.getStoredUser(); + serie = serie || user?.passport_serie; + pid = pid || user?.passport_id; + } + if (!serie || !pid) { + throw new Error('Passport details are missing'); + } + const payload = { + passport_serie: serie, + passport_id: pid, + card_number: cardNumber, + card_month: cardMonth, + card_year: cardYear, + }; + return this.makeRequest(`/card-transactions/${orderId}`, payload, true, 'POST'); + } + + // DELETE + async deleteCardTransactionOrder(orderId) { + return this.makeRequest(`/card-transactions/${orderId}`, null, true, 'DELETE'); + } + + // DOWNLOAD (returns object with url) + async downloadCardTransactions(orderId, startDate, endDate) { + const query = `start_date=${encodeURIComponent(startDate)}&end_date=${encodeURIComponent(endDate)}`; + return this.makeRequest(`/card-transactions-download/${orderId}?${query}`, null, true, 'GET'); + } } export default new AuthService(); \ No newline at end of file