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