From a6531ca88a5becb754d1eaf7fe0490d425f655c5 Mon Sep 17 00:00:00 2001 From: fakz9 Date: Fri, 23 Feb 2024 13:34:06 +0300 Subject: [PATCH] feat: reward --- app.json | 2 +- package.json | 4 +- src/App.tsx | 14 +- src/api/apiClient.ts | 27 +--- src/api/assemblyApi.ts | 2 +- src/api/balanceApi.ts | 6 +- src/assets/icons/reward.png | Bin 0 -> 18305 bytes src/components/Animations/AnimationsView.tsx | 93 ++++++++++++ src/components/DTitle/DTitle.tsx | 9 +- src/contexts/apiContext.tsx | 2 +- src/features/animations/animationsSlice.ts | 29 ++++ src/features/balance/balanceSlice.ts | 69 ++++++--- src/features/balance/service.ts | 15 -- src/redux/store.ts | 6 +- src/screens/CommonPage/CommonPage.tsx | 39 ++++- src/screens/MainScreen/MainScreen.tsx | 15 +- src/screens/OrderScreen/OrderScreen.tsx | 17 ++- src/screens/ProfileScreen/ProfileScreen.tsx | 139 ++++-------------- src/screens/ProfileScreen/SettingsView.tsx | 4 +- src/screens/ProfileScreen/TransactionView.tsx | 13 +- src/types/balance.ts | 4 + src/utils/formatters.ts | 8 + 22 files changed, 309 insertions(+), 208 deletions(-) create mode 100644 src/assets/icons/reward.png create mode 100644 src/components/Animations/AnimationsView.tsx create mode 100644 src/features/animations/animationsSlice.ts delete mode 100644 src/features/balance/service.ts create mode 100644 src/utils/formatters.ts diff --git a/app.json b/app.json index 93344ef..0829d11 100644 --- a/app.json +++ b/app.json @@ -15,7 +15,7 @@ ], "name": "Assemblr", "slug": "Assemblr", - "version": "1.1.7", + "version": "1.3.4", "orientation": "portrait", "icon": "./src/assets/icon.png", "userInterfaceStyle": "light", diff --git a/package.json b/package.json index c973af9..f694795 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-native": "0.72.6", + "react-native-animatable": "^1.4.0", "react-native-gesture-handler": "~2.12.0", "react-native-image-zoom-viewer": "^3.0.1", "react-native-keyevent": "^0.3.1", @@ -58,7 +59,8 @@ "react-native-webview": "13.2.2", "react-redux": "^8.1.2", "redux": "^4.2.1", - "rn-openapp": "^2.1.2" + "rn-openapp": "^2.1.2", + "expo-av": "~13.4.1" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/src/App.tsx b/src/App.tsx index 6a0732d..4a7287e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,28 +1,18 @@ -import {StyleSheet, Text, View} from 'react-native'; +import {StyleSheet} from 'react-native'; import {Provider} from "react-redux"; - -import {useFonts} from 'expo-font'; import {store} from "./redux/store"; import React from "react"; import CommonPage from "./screens/CommonPage/CommonPage"; import {BottomSheetModalProvider} from "@gorhom/bottom-sheet"; import {GestureHandlerRootView} from "react-native-gesture-handler"; -import Toast from "react-native-toast-message"; -import Constants from "expo-constants"; export default function App() { - let [fontsLoading] = useFonts({ - // 'SF Pro Text': require('./assets/fonts/SF-Pro-Text-Regular.otf') - }) - if (!fontsLoading) - return Loading... return ( - - + diff --git a/src/api/apiClient.ts b/src/api/apiClient.ts index 20ad3a9..cba44b6 100644 --- a/src/api/apiClient.ts +++ b/src/api/apiClient.ts @@ -1,33 +1,10 @@ -import axios, {AxiosHeaders, AxiosRequestConfig, AxiosRequestHeaders, InternalAxiosRequestConfig} from 'axios'; -import * as SecureStore from 'expo-secure-store'; -import {useDispatch} from "react-redux"; -import {logout} from "../features/auth/authSlice"; -import {store} from "../redux/store"; +import axios from 'axios'; + export const baseUrl = 'https://assemblr.denco.store'; // export const baseUrl = 'http://192.168.1.101:5000'; const apiClient = axios.create({ baseURL: baseUrl }); -apiClient.interceptors.request.use(async (config) => { - const accessToken = await SecureStore.getItemAsync('accessToken'); - if (!config.headers) { - config.headers = new AxiosHeaders(); - } - if (accessToken) { - config.headers.set('Authorization', `Bearer ${accessToken}`, true); - } - config.validateStatus = (status) => { - if (status == 401) { - SecureStore.deleteItemAsync('accessToken'); - store.dispatch(logout()); - } - return true; - }; - - return config; -}, function (error) { -}); - export default apiClient; diff --git a/src/api/assemblyApi.ts b/src/api/assemblyApi.ts index 4ce3db3..d3cf8db 100644 --- a/src/api/assemblyApi.ts +++ b/src/api/assemblyApi.ts @@ -15,7 +15,7 @@ const assemblyApi = { let response = await apiClient.post(`${router}/create`, {orderId}); return response.data; }, - close: async (assemblyId: number): Promise<{ ok: boolean, message: string }> => { + close: async (assemblyId: number): Promise<{ ok: boolean, message: string, reward: number }> => { let response = await apiClient.post(`${router}/close`, {assemblyId}); return response.data; }, diff --git a/src/api/balanceApi.ts b/src/api/balanceApi.ts index 2cbc4b5..cb67fbb 100644 --- a/src/api/balanceApi.ts +++ b/src/api/balanceApi.ts @@ -1,4 +1,4 @@ -import {BalanceTransaction} from "../types/balance"; +import {BalanceInfo, BalanceTransaction} from "../types/balance"; import apiClient from "./apiClient"; const router = '/balance'; @@ -6,6 +6,10 @@ const balanceApi = { getTransactions: async (page: number): Promise<{ balanceTransactions: BalanceTransaction[] }> => { const response = await apiClient.get(`${router}/transactions`, {params: {page}}); return response.data; + }, + getBalanceInfo: async (): Promise => { + const response = await apiClient.get(`${router}/info`); + return response.data; } } diff --git a/src/assets/icons/reward.png b/src/assets/icons/reward.png new file mode 100644 index 0000000000000000000000000000000000000000..72958bab5472214b86bb1637e308bdccf3f1cfd1 GIT binary patch literal 18305 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 Y0~&5@gU^~Zy8r+H07*qoM6N<$f}-`3#{d8T literal 0 HcmV?d00001 diff --git a/src/components/Animations/AnimationsView.tsx b/src/components/Animations/AnimationsView.tsx new file mode 100644 index 0000000..ce04a5e --- /dev/null +++ b/src/components/Animations/AnimationsView.tsx @@ -0,0 +1,93 @@ +import {FC, useEffect, useState} from "react"; +import {StyleSheet, View, Image} from "react-native"; +import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions"; +import * as Animatable from 'react-native-animatable'; +import DText from "../DText/DText"; +import {useSelector} from "react-redux"; +import {RootState, useAppDispatch} from "../../redux/store"; +import {hideReward} from "../../features/animations/animationsSlice"; + +// const width = responsiveWidth(20) +const AnimationsView: FC = () => { + const dispatch = useAppDispatch(); + const state = useSelector((state: RootState) => state.animations); + const [currentAnimation, setCurrentAnimation] = useState('bounceInRight'); + const [animationStage, setAnimationStage] = useState('in'); // Управляет текущей стадией анимации + + useEffect(() => { + // После завершения анимации входа, начинаем анимацию выхода + if (animationStage === 'out') { + setCurrentAnimation('bounceOutRight'); + } + }, [animationStage]); + + const onAnimationEnd = () => { + if (animationStage === 'in') { + // После завершения анимации входа, переходим к анимации выхода + setAnimationStage('out'); + } else if (animationStage === "out") { + dispatch(hideReward()); + } + }; + + const startAnimation = () => { + setCurrentAnimation("bounceInRight"); + setAnimationStage('in'); + } + + useEffect(() => { + if (state.isRewardAnimationVisible) + startAnimation(); + + }, [state.isRewardAnimationVisible]); + + return ( + + + + + + +{state.rewardAmount.toLocaleString('ru-RU')}₽ + + + + ) +} + +const style = StyleSheet.create({ + container: { + position: "absolute", + right: 0, + top: 0, + marginTop: responsiveHeight(10), + + } +}) +export default AnimationsView; \ No newline at end of file diff --git a/src/components/DTitle/DTitle.tsx b/src/components/DTitle/DTitle.tsx index 299c3d5..86b536b 100644 --- a/src/components/DTitle/DTitle.tsx +++ b/src/components/DTitle/DTitle.tsx @@ -1,11 +1,10 @@ -import React, {FC, ReactElement} from "react"; -import {StyleProp, StyleSheet, Text, TextStyle, View, ViewStyle} from 'react-native'; -import {RFPercentage} from "react-native-responsive-fontsize"; +import React, {FC, ReactNode} from "react"; +import {StyleProp, StyleSheet, TextStyle} from 'react-native'; import DText from "../DText/DText"; -import {responsiveScreenFontSize, responsiveWidth} from "react-native-responsive-dimensions"; +import {responsiveScreenFontSize} from "react-native-responsive-dimensions"; type Props = { - children: string; + children: ReactNode; style?: StyleProp; } const DTitle: FC = ({children, style}) => { diff --git a/src/contexts/apiContext.tsx b/src/contexts/apiContext.tsx index 17b85a8..0d2f3a6 100644 --- a/src/contexts/apiContext.tsx +++ b/src/contexts/apiContext.tsx @@ -5,7 +5,7 @@ type ApiState = { }; const apiContext = createContext(undefined); -const useApiContext = () => { +export const useApiContext = () => { const context = useContext(apiContext); if (!context) throw new Error('useApiContext must be used within ApiContextProvider'); return context; diff --git a/src/features/animations/animationsSlice.ts b/src/features/animations/animationsSlice.ts new file mode 100644 index 0000000..2b0e029 --- /dev/null +++ b/src/features/animations/animationsSlice.ts @@ -0,0 +1,29 @@ +import {createSlice, PayloadAction} from "@reduxjs/toolkit"; + +interface AnimationsSlice { + isRewardAnimationVisible: boolean; + rewardAmount: number; +} + +const initialState: AnimationsSlice = { + isRewardAnimationVisible: false, + rewardAmount: 0 +} + +export const animationsSlice = createSlice({ + name: 'animations', + initialState, + reducers: { + showReward: (state, action: PayloadAction<{ reward: number }>) => { + state.rewardAmount = action.payload.reward; + state.isRewardAnimationVisible = true; + }, + hideReward: (state) => { + state.isRewardAnimationVisible = false; + state.rewardAmount = 0; + } + } +}) + +export const {showReward, hideReward} = animationsSlice.actions; +export default animationsSlice.reducer; \ No newline at end of file diff --git a/src/features/balance/balanceSlice.ts b/src/features/balance/balanceSlice.ts index bd10df2..d00c621 100644 --- a/src/features/balance/balanceSlice.ts +++ b/src/features/balance/balanceSlice.ts @@ -1,36 +1,71 @@ -import {BalanceTransaction} from "../../types/balance"; -import {createAsyncThunk, createSlice, PayloadAction} from "@reduxjs/toolkit"; -import {RootState} from "../../redux/store"; +import {BalanceInfo, BalanceTransaction} from "../../types/balance"; +import {createAsyncThunk, createSlice} from "@reduxjs/toolkit"; +import balanceApi from "../../api/balanceApi"; const name = 'balance'; +interface TransactionsState { + items: BalanceTransaction[]; + isLoading: boolean; + currentPage: number; + hasNext: boolean; +} + interface BalanceState { - transactions: BalanceTransaction[]; balance: number; - page: number; - loading: boolean; + transactions: TransactionsState; } +const transactionsInitialState: TransactionsState = { + currentPage: 1, + hasNext: true, + isLoading: false, + items: [] +} const initialState: BalanceState = { - transactions: [], balance: 0, - page: 1, - loading: false + transactions: transactionsInitialState } + +export const fetchTransactions = createAsyncThunk( + `${name}/fetchTransactions`, + async (page: number, _): Promise => { + const response = await balanceApi.getTransactions(page); + return response.balanceTransactions; + } +) + +export const fetchBalance = createAsyncThunk( + `${name}/fetchBalance`, + async (_): Promise => { + return await balanceApi.getBalanceInfo(); + } +) export const balanceSlice = createSlice({ name: name, initialState, reducers: { - appendTransactions: (state, payload: PayloadAction) => { - state.transactions.push(...payload.payload); - state.page = state.page + 1; - state.loading = false - }, - setIsLoading: (state, action: PayloadAction) => { - state.loading = action.payload; + refreshTransactions: (state) => { + state.transactions = transactionsInitialState; } }, + extraReducers: (builder) => { + builder.addCase(fetchTransactions.pending, (state, action) => { + state.transactions.isLoading = true; + }) + builder.addCase(fetchTransactions.fulfilled, (state, action) => { + state.transactions.isLoading = false; + state.transactions.hasNext = action.payload.length > 0; + state.transactions.items = [...state.transactions.items, ...action.payload]; + state.transactions.currentPage = state.transactions.currentPage + 1; + }) + builder.addCase(fetchBalance.fulfilled, (state, action) => { + state.balance = action.payload.balance; + }) + } }) -export const {appendTransactions, setIsLoading} = balanceSlice.actions; + +export const {refreshTransactions} = balanceSlice.actions; + export default balanceSlice.reducer; \ No newline at end of file diff --git a/src/features/balance/service.ts b/src/features/balance/service.ts deleted file mode 100644 index 260c5a8..0000000 --- a/src/features/balance/service.ts +++ /dev/null @@ -1,15 +0,0 @@ -import {createApi} from '@reduxjs/toolkit/query/react' - - -import {axiosBaseQuery} from "../../redux/general"; -import {EndpointBuilder} from "@reduxjs/toolkit/dist/query/endpointDefinitions"; -import {BalanceTransaction} from "../../types/balance"; - -export const balanceApi = createApi({ - reducerPath: 'balanceApi', - baseQuery: axiosBaseQuery(), - endpoints: (builder) => ({ - getTransactions: builder.query({query: () => ({url: "/transactions", method: 'get'})}) - }) -}) -export const {useGetTransactionsQuery} = balanceApi; \ No newline at end of file diff --git a/src/redux/store.ts b/src/redux/store.ts index 074aacd..6dd1c6b 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -12,6 +12,8 @@ import ordersFilterReducer from 'features/ordersFilter/ordersFilterSlice'; import shippingWarehouseSelectReducer from 'features/shippingWarehouseSelect/shippingWarehouseSelectSlice'; import citySelectReducer from 'features/citySelect/citySelectSlice'; import cancelAssemblyModal from 'features/cancelAssemblyModal/cancelAssemblyModalSlice'; +import balanceReducer from 'features/balance/balanceSlice'; +import animationsReducer from 'features/animations/animationsSlice'; import {useDispatch} from "react-redux"; export const store = configureStore({ @@ -27,7 +29,9 @@ export const store = configureStore({ ordersFilter: ordersFilterReducer, shippingWarehouseSelect: shippingWarehouseSelectReducer, citySelect: citySelectReducer, - cancelAssemblyModal: cancelAssemblyModal + cancelAssemblyModal: cancelAssemblyModal, + balance: balanceReducer, + animations: animationsReducer }, }); diff --git a/src/screens/CommonPage/CommonPage.tsx b/src/screens/CommonPage/CommonPage.tsx index be0f491..ac6665b 100644 --- a/src/screens/CommonPage/CommonPage.tsx +++ b/src/screens/CommonPage/CommonPage.tsx @@ -5,7 +5,7 @@ import React, {useEffect} from "react"; import {background} from "../../css/colors"; import {useDispatch, useSelector} from "react-redux"; import {RootState} from "../../redux/store"; -import {login} from "../../features/auth/authSlice"; +import {login, logout} from "../../features/auth/authSlice"; import * as SecureStore from 'expo-secure-store'; import Toast from "react-native-toast-message"; import toastConfig from "../../components/Toast/Toast"; @@ -21,9 +21,7 @@ import {ASSEMBLY_STATE} from "../../types/assembly"; import Constants from "expo-constants"; import * as FileSystem from 'expo-file-system'; import applicationApi from "../../api/applicationApi"; -import {ActivityAction, startActivityAsync} from "expo-intent-launcher"; -import {RenderTargetOptions} from "@shopify/flash-list"; -import KeyEvent from 'react-native-keyevent'; +import {startActivityAsync} from "expo-intent-launcher"; import { openLoadingModal, setIndeterminate, @@ -31,8 +29,12 @@ import { setProgress } from "../../features/loadingModal/loadingModalSlice"; import CancelAssemblyModal from "../../components/Modals/CancelAssemblyModal/CancelAssemblyModal"; +import {AxiosHeaders} from "axios"; +import apiClient from "../../api/apiClient"; +import AnimationsView from "../../components/Animations/AnimationsView"; function CommonPage() { + const dim = useSelector((state: RootState) => state.interface.dim); const isAuthorized = useSelector((state: RootState) => state.auth.isAuthorized); @@ -85,13 +87,42 @@ function CommonPage() { }) }); } + + + const configApi = () => { + + apiClient.interceptors.request.use(async (config) => { + const accessToken = await SecureStore.getItemAsync('accessToken'); + if (!config.headers) { + config.headers = new AxiosHeaders(); + } + if (accessToken) { + config.headers.set('Authorization', `Bearer ${accessToken}`, true); + } + config.validateStatus = (status) => { + if (status == 401) { + SecureStore.deleteItemAsync('accessToken'); + dispatch(logout()); + } + return true; + }; + + return config; + }, function (error) { + }); + } + useEffect(() => { + configApi(); checkUpdates(); + loadSettings(); }, []); return ( + {isAuthorized ? : } + diff --git a/src/screens/MainScreen/MainScreen.tsx b/src/screens/MainScreen/MainScreen.tsx index 56f13ad..3182c0c 100644 --- a/src/screens/MainScreen/MainScreen.tsx +++ b/src/screens/MainScreen/MainScreen.tsx @@ -1,4 +1,4 @@ -import {StyleSheet, Text, View, ImageBackground, Image} from 'react-native'; +import {StyleSheet, Image} from 'react-native'; import {createBottomTabNavigator} from "@react-navigation/bottom-tabs"; import BarcodeScreen from "../BarcodeScreen/BarcodeScreen"; import {DefaultTheme, NavigationContainer} from "@react-navigation/native"; @@ -6,10 +6,10 @@ import moneyScreen from "../MoneyScreen/MoneyScreen"; import profileScreen from "../ProfileScreen/ProfileScreen"; import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions"; import {RFPercentage} from "react-native-responsive-fontsize"; -import OrderScreen, {OrderScreenController} from "../OrderScreen/OrderScreen"; +import {OrderScreenController} from "../OrderScreen/OrderScreen"; import OrdersScreen from "../OrdersScreen/OrdersScreen"; import {background} from "../../css/colors"; -import * as fs from "fs"; + export type TabNavigatorParamList = { Home: undefined; @@ -30,16 +30,16 @@ const CustomTab = ({name, component, icon, hidden}: CustomTabProps) => ({ name, component, options: { - tabBarIcon: ({focused, color, size}: { focused: boolean; color: string; size: number }) => ( - { + return ( - ), + />) + }, tabBarLabel: () => null, } }); @@ -90,6 +90,7 @@ function MainScreen() { }, }}> + = ({order}) => { const navigator = useNavigation>(); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const assembly = useSelector((state: RootState) => state.assembly.assembly); const assemblyState = useSelector((state: RootState) => state.assembly.localState); const initialOrder = useSelector((state: RootState) => state.assembly.initialOrder); @@ -144,12 +146,19 @@ const OrderScreen: FC = ({order}) => { label={"Завершить сборку"} onPress={() => { if (!assembly) return; - assemblyApi.close(assembly.databaseId).then(({ok, message}) => { + assemblyApi.close(assembly.databaseId).then(({ok, message, reward}) => { Toast.show({ type: ok ? 'success' : 'error', text1: 'Завершение сборки', text2: message }); + if (ok) { + dispatch(showReward({reward})); + dispatch(fetchBalance()) + dispatch(refreshTransactions()) + + } + dispatch(endAssembly()); navigator.navigate('Barcode'); }) diff --git a/src/screens/ProfileScreen/ProfileScreen.tsx b/src/screens/ProfileScreen/ProfileScreen.tsx index 490ce6e..9c6f5c9 100644 --- a/src/screens/ProfileScreen/ProfileScreen.tsx +++ b/src/screens/ProfileScreen/ProfileScreen.tsx @@ -1,130 +1,54 @@ -import {View, StyleSheet, TouchableOpacity, Image, ScrollView, GestureResponderEvent} from "react-native"; -import {responsiveFontSize, responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions"; -import {blue, gray} from "../../css/colors"; +import {View, StyleSheet} from "react-native"; +import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions"; +import {blue} from "../../css/colors"; import {RFPercentage} from "react-native-responsive-fontsize"; -import DText from "../../components/DText/DText"; import DTitle from "../../components/DTitle/DTitle"; import Separator from "../../components/Separator/Separator"; -import {BottomSheetModal} from "@gorhom/bottom-sheet"; -import {useMemo, useRef, useState} from "react"; -import assemblyApi from "../../api/assemblyApi"; -import Toast from "react-native-toast-message"; -import {useDispatch} from "react-redux"; -import {reset} from "../../features/assembly/assemblySlice"; +import {useEffect} from "react"; +import {useSelector} from "react-redux"; +import SettingsView from "./SettingsView"; +import TransactionsView from "./TransactionView"; +import {RootState, useAppDispatch} from "../../redux/store"; +import {fetchBalance, fetchTransactions, refreshTransactions} from "../../features/balance/balanceSlice"; +import {formatBalanceNumber} from "../../utils/formatters"; -type SettingsElementProps = { - icon: any; - title: string; - onPress?: (event: GestureResponderEvent) => void - -} -const SettingsElement: React.FC = ({icon, title, onPress}) => { - return ( - - - - - - - {title} - - - - ) -} -type HistoryElementProps = { - cost: number; - description: string; - color: string; -} -const HistoryElement: React.FC = ({cost, description, color}) => { - const formatNumber = (n: number): string => n >= 0 ? `+${n}` : `${n}`; - return ( - - {formatNumber(cost)} руб - {description} - - ) -} - function ProfileScreen() { - - const bottomSheetModalRef = useRef(null); - const snapPoints = useMemo(() => ['25%', '40%'], []); - const [modalVisible, setModalVisible] = useState(false); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); + const balanceState = useSelector((state: RootState) => state.balance); + const onTransactionsEndReached = () => { + if (balanceState.transactions.isLoading || !balanceState.transactions.hasNext) return; + dispatch(fetchTransactions(balanceState.transactions.currentPage)); + }; + const onTransactionsRefresh = () => { + dispatch(refreshTransactions()); + } + useEffect(() => { + dispatch(fetchBalance()); + }, []); return ( - Ваш баланс: 228 руб - Собрано товаров: 1337 шт + Ваш + баланс: {formatBalanceNumber(balanceState.balance, false)} руб - - - - - { - assemblyApi.cancel().then(response => { - - Toast.show({ - type: response.ok ? "success" : "error", - text1: "Отмена сборки", - text2: response.message, - }); - dispatch(reset()); - }) - }} icon={require('assets/icons/settings/close.png')} title={'Отменить сборку'}/> - + История операций - - - - - - - - - - - - - + - { - setModalVisible(false); - }} - > - - хуй - - - - - ) } @@ -151,7 +75,8 @@ const styles = StyleSheet.create({ historyContainer: { paddingVertical: responsiveHeight(2), flex: 1, - // backgroundColor: "red" + gap: responsiveHeight(1), + marginBottom: responsiveHeight(6) }, historyElements: { marginTop: responsiveHeight(3), diff --git a/src/screens/ProfileScreen/SettingsView.tsx b/src/screens/ProfileScreen/SettingsView.tsx index 919f6b6..28fd0a6 100644 --- a/src/screens/ProfileScreen/SettingsView.tsx +++ b/src/screens/ProfileScreen/SettingsView.tsx @@ -22,7 +22,7 @@ const SettingsElement: FC = ({icon, title, onPress}) => { - {title} + {title} @@ -59,10 +59,12 @@ const styles = StyleSheet.create({ actionsCarouselElementContainer: { display: "flex", alignItems: "center", + maxWidth: RFPercentage(10), }, actionsCarouselImageWrapper: { width: RFPercentage(10), height: RFPercentage(10), + backgroundColor: "white", borderRadius: 100, borderWidth: RFPercentage(0.3), diff --git a/src/screens/ProfileScreen/TransactionView.tsx b/src/screens/ProfileScreen/TransactionView.tsx index 1480600..89885cc 100644 --- a/src/screens/ProfileScreen/TransactionView.tsx +++ b/src/screens/ProfileScreen/TransactionView.tsx @@ -7,18 +7,19 @@ import {FC} from "react"; import {RFPercentage} from "react-native-responsive-fontsize"; import {FlashList} from "@shopify/flash-list"; import flashListSeparator from "../../components/FlashListSeparator/FlashListSeparator"; +import {formatBalanceNumber} from "../../utils/formatters"; type TransactionElementProps = { transaction: BalanceTransaction; } const TransactionElement: FC = ({transaction}) => { - const formatNumber = (n: number): string => n >= 0 ? `+${n}` : `${n}`; + return ( {formatNumber(transaction.amount)} руб + }}>{formatBalanceNumber(transaction.amount)} руб = ({transaction}) => { type TransactionsViewProps = { transactions: BalanceTransaction[]; onEndReached: () => void; + onRefresh: () => void; + isLoading: boolean; } -const TransactionsView: FC = ({transactions, onEndReached}) => { - console.log('----------------------------------') - console.log(JSON.stringify(transactions, null, 2)) +const TransactionsView: FC = ({transactions, isLoading, onEndReached, onRefresh}) => { return ( = ({transactions, onEndReached transaction={item.item} />} onEndReached={onEndReached} + refreshing={isLoading} > ) diff --git a/src/types/balance.ts b/src/types/balance.ts index b49a11c..3569df5 100644 --- a/src/types/balance.ts +++ b/src/types/balance.ts @@ -11,4 +11,8 @@ export type BalanceTransaction = { description: string; jsonData?: object; createdAt: string; +} + +export type BalanceInfo = { + balance: number; } \ No newline at end of file diff --git a/src/utils/formatters.ts b/src/utils/formatters.ts new file mode 100644 index 0000000..f908c02 --- /dev/null +++ b/src/utils/formatters.ts @@ -0,0 +1,8 @@ +export const formatBalanceNumber = (n: number, addSign = true) => { + const roundNumber = (n: number, digits: number): number => parseInt(n.toFixed(digits)) + const formatNumber = (n: number): string => n + >= 0 ? `${addSign ? '+' : ''}${roundNumber(n, 2).toLocaleString('ru-RU')}` : + `${roundNumber(n, 2).toLocaleString('ru-RU')}`; + + return formatNumber(n); +} \ No newline at end of file