feat: reward
This commit is contained in:
		
							
								
								
									
										2
									
								
								app.json
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								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",
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								src/App.tsx
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								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 <View><Text>Loading...</Text></View>
 | 
			
		||||
    return (
 | 
			
		||||
        <Provider store={store}>
 | 
			
		||||
            <GestureHandlerRootView style={{flex: 1}}>
 | 
			
		||||
                <BottomSheetModalProvider>
 | 
			
		||||
                    <CommonPage/>
 | 
			
		||||
 | 
			
		||||
                        <CommonPage/>
 | 
			
		||||
                </BottomSheetModalProvider>
 | 
			
		||||
            </GestureHandlerRootView>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -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<BalanceInfo> => {
 | 
			
		||||
        const response = await apiClient.get(`${router}/info`);
 | 
			
		||||
        return response.data;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								src/assets/icons/reward.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/assets/icons/reward.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 18 KiB  | 
							
								
								
									
										93
									
								
								src/components/Animations/AnimationsView.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								src/components/Animations/AnimationsView.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -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 (
 | 
			
		||||
        <View style={{...style.container, display: state.isRewardAnimationVisible ? "flex" : "none"}}>
 | 
			
		||||
            <Animatable.View
 | 
			
		||||
                style={{
 | 
			
		||||
                    backgroundColor: "white",
 | 
			
		||||
                    borderRadius: responsiveWidth(3),
 | 
			
		||||
                    // width: width,
 | 
			
		||||
                    paddingHorizontal: responsiveWidth(2),
 | 
			
		||||
                    display: "flex",
 | 
			
		||||
                    flexDirection: "row",
 | 
			
		||||
                    alignItems: "center",
 | 
			
		||||
                    gap: responsiveWidth(1),
 | 
			
		||||
                    zIndex: 1,
 | 
			
		||||
                    elevation: 10,
 | 
			
		||||
                    justifyContent: "center"
 | 
			
		||||
                }}
 | 
			
		||||
                animation={currentAnimation}
 | 
			
		||||
                duration={1000} // Продолжительность анимации
 | 
			
		||||
                easing="ease-in-out" // Тип анимации
 | 
			
		||||
                iterationCount={1} // Бесконечное повторение
 | 
			
		||||
                onAnimationEnd={onAnimationEnd}
 | 
			
		||||
            >
 | 
			
		||||
                <View style={{
 | 
			
		||||
                    height: responsiveHeight(5)
 | 
			
		||||
                }}>
 | 
			
		||||
                    <Image
 | 
			
		||||
                        style={{
 | 
			
		||||
                            height: responsiveHeight(5),
 | 
			
		||||
                            width:responsiveWidth(5),
 | 
			
		||||
                            resizeMode: "contain"
 | 
			
		||||
                        }}
 | 
			
		||||
                        source={require('assets/icons/reward.png')}/>
 | 
			
		||||
                </View>
 | 
			
		||||
                <DText>+{state.rewardAmount.toLocaleString('ru-RU')}₽</DText>
 | 
			
		||||
 | 
			
		||||
            </Animatable.View>
 | 
			
		||||
        </View>
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const style = StyleSheet.create({
 | 
			
		||||
    container: {
 | 
			
		||||
        position: "absolute",
 | 
			
		||||
        right: 0,
 | 
			
		||||
        top: 0,
 | 
			
		||||
        marginTop: responsiveHeight(10),
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
export default AnimationsView;
 | 
			
		||||
@@ -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<TextStyle>;
 | 
			
		||||
}
 | 
			
		||||
const DTitle: FC<Props> = ({children, style}) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ type ApiState = {
 | 
			
		||||
};
 | 
			
		||||
const apiContext = createContext<ApiState | undefined>(undefined);
 | 
			
		||||
 | 
			
		||||
const useApiContext = () => {
 | 
			
		||||
export const useApiContext = () => {
 | 
			
		||||
    const context = useContext(apiContext);
 | 
			
		||||
    if (!context) throw new Error('useApiContext must be used within ApiContextProvider');
 | 
			
		||||
    return context;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										29
									
								
								src/features/animations/animationsSlice.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/features/animations/animationsSlice.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
@@ -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<BalanceTransaction[]> => {
 | 
			
		||||
        const response = await balanceApi.getTransactions(page);
 | 
			
		||||
        return response.balanceTransactions;
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
export const fetchBalance = createAsyncThunk(
 | 
			
		||||
    `${name}/fetchBalance`,
 | 
			
		||||
    async (_): Promise<BalanceInfo> => {
 | 
			
		||||
        return await balanceApi.getBalanceInfo();
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
export const balanceSlice = createSlice({
 | 
			
		||||
    name: name,
 | 
			
		||||
    initialState,
 | 
			
		||||
    reducers: {
 | 
			
		||||
        appendTransactions: (state, payload: PayloadAction<BalanceTransaction[]>) => {
 | 
			
		||||
            state.transactions.push(...payload.payload);
 | 
			
		||||
            state.page = state.page + 1;
 | 
			
		||||
            state.loading = false
 | 
			
		||||
        },
 | 
			
		||||
        setIsLoading: (state, action: PayloadAction<boolean>) => {
 | 
			
		||||
            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;
 | 
			
		||||
@@ -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;
 | 
			
		||||
@@ -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
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 (
 | 
			
		||||
 | 
			
		||||
        <View style={styles.main}>
 | 
			
		||||
            {isAuthorized ? <MainScreen/> : <LoginScreen/>}
 | 
			
		||||
            <AnimationsView/>
 | 
			
		||||
            <View style={[styles.overlay, {display: dim ? 'flex' : 'none'}]}/>
 | 
			
		||||
            <LoadingModal/>
 | 
			
		||||
            <ScanModal/>
 | 
			
		||||
 
 | 
			
		||||
@@ -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 }) => (
 | 
			
		||||
            <Image
 | 
			
		||||
        tabBarIcon: ({focused, color, size}: { focused: boolean; color: string; size: number }) => {
 | 
			
		||||
            return (<Image
 | 
			
		||||
                source={icon}
 | 
			
		||||
                style={{
 | 
			
		||||
                    width: RFPercentage(4),
 | 
			
		||||
                    height: RFPercentage(4),
 | 
			
		||||
                    tintColor: color,
 | 
			
		||||
                }}
 | 
			
		||||
            />
 | 
			
		||||
        ),
 | 
			
		||||
            />)
 | 
			
		||||
        },
 | 
			
		||||
        tabBarLabel: () => null,
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
@@ -90,6 +90,7 @@ function MainScreen() {
 | 
			
		||||
            },
 | 
			
		||||
        }}>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            <Tab.Navigator screenOptions={{
 | 
			
		||||
                tabBarStyle: styles.tabBarStyle,
 | 
			
		||||
                headerTitle: "",
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,8 @@ import {RFPercentage} from "react-native-responsive-fontsize";
 | 
			
		||||
import DTitle from "../../components/DTitle/DTitle";
 | 
			
		||||
import BasicButton from "../../components/BasicButton/BasicButton";
 | 
			
		||||
import React, {FC, useEffect, useState} from "react";
 | 
			
		||||
import {useDispatch, useSelector} from "react-redux";
 | 
			
		||||
import {RootState} from "../../redux/store";
 | 
			
		||||
import {useSelector} from "react-redux";
 | 
			
		||||
import {RootState, useAppDispatch} from "../../redux/store";
 | 
			
		||||
import {closeLoadingModal, openLoadingModal, setLoadingText} from "../../features/loadingModal/loadingModalSlice";
 | 
			
		||||
import {NavigationProp, useNavigation} from "@react-navigation/native";
 | 
			
		||||
import printingApi from "../../api/printingApi";
 | 
			
		||||
@@ -31,6 +31,8 @@ import {TabNavigatorParamList} from "../MainScreen/MainScreen";
 | 
			
		||||
import {openImageZoomModal, setImages} from "../../features/imageZoomModal/loadingModalSlice";
 | 
			
		||||
import {OrderStatus, OrderStatusDictionary} from "../../features/ordersFilter/ordersFilterSlice";
 | 
			
		||||
import {openCancelAssemblyModal} from "../../features/cancelAssemblyModal/cancelAssemblyModalSlice";
 | 
			
		||||
import {fetchBalance, refreshTransactions} from "../../features/balance/balanceSlice";
 | 
			
		||||
import {showReward} from "../../features/animations/animationsSlice";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type AssemblyPeriod = {
 | 
			
		||||
@@ -68,7 +70,7 @@ type OrderScreenProps = {
 | 
			
		||||
const OrderScreen: FC<OrderScreenProps> = ({order}) => {
 | 
			
		||||
    const navigator = useNavigation<NavigationProp<TabNavigatorParamList, 'Barcode'>>();
 | 
			
		||||
 | 
			
		||||
    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<OrderScreenProps> = ({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');
 | 
			
		||||
                                })
 | 
			
		||||
 
 | 
			
		||||
@@ -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<SettingsElementProps> = ({icon, title, onPress}) => {
 | 
			
		||||
    return (
 | 
			
		||||
        <TouchableOpacity onPress={onPress}>
 | 
			
		||||
 | 
			
		||||
            <View style={styles.actionsCarouselElementContainer}>
 | 
			
		||||
                <View style={styles.actionsCarouselImageWrapper}>
 | 
			
		||||
                    <Image style={styles.actionsCarouselImage} source={icon}/>
 | 
			
		||||
                </View>
 | 
			
		||||
                <DText>{title}</DText>
 | 
			
		||||
            </View>
 | 
			
		||||
        </TouchableOpacity>
 | 
			
		||||
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
type HistoryElementProps = {
 | 
			
		||||
    cost: number;
 | 
			
		||||
    description: string;
 | 
			
		||||
    color: string;
 | 
			
		||||
}
 | 
			
		||||
const HistoryElement: React.FC<HistoryElementProps> = ({cost, description, color}) => {
 | 
			
		||||
    const formatNumber = (n: number): string => n >= 0 ? `+${n}` : `${n}`;
 | 
			
		||||
    return (
 | 
			
		||||
        <View style={styles.historyElementContainer}>
 | 
			
		||||
            <DText style={{
 | 
			
		||||
                fontSize: responsiveFontSize(2),
 | 
			
		||||
                color: color,
 | 
			
		||||
                fontWeight: "500"
 | 
			
		||||
            }}>{formatNumber(cost)} руб</DText>
 | 
			
		||||
            <DText style={{fontSize: responsiveFontSize(1.5), color: gray, fontWeight: "400"}}>{description}</DText>
 | 
			
		||||
        </View>
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function ProfileScreen() {
 | 
			
		||||
 | 
			
		||||
    const bottomSheetModalRef = useRef<BottomSheetModal>(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 (
 | 
			
		||||
        <View style={styles.container}>
 | 
			
		||||
            <View style={styles.header}>
 | 
			
		||||
                <View style={styles.headerInfo}>
 | 
			
		||||
                    <DTitle style={styles.headerText}>Ваш баланс: 228 руб</DTitle>
 | 
			
		||||
                    <DTitle style={styles.headerText}>Собрано товаров: 1337 шт</DTitle>
 | 
			
		||||
                    <DTitle style={styles.headerText}>Ваш
 | 
			
		||||
                        баланс: {formatBalanceNumber(balanceState.balance, false)} руб</DTitle>
 | 
			
		||||
                </View>
 | 
			
		||||
 | 
			
		||||
            </View>
 | 
			
		||||
            <View style={styles.content}>
 | 
			
		||||
                <View style={styles.actionsCarouselContainer}>
 | 
			
		||||
                    <ScrollView horizontal={true}
 | 
			
		||||
                                showsVerticalScrollIndicator={false}
 | 
			
		||||
                                showsHorizontalScrollIndicator={false}
 | 
			
		||||
                                contentContainerStyle={styles.actionsCarousel}
 | 
			
		||||
                    >
 | 
			
		||||
                        <SettingsElement icon={require('assets/icons/settings/withdraw.png')} title={'Вывод'}/>
 | 
			
		||||
                        <SettingsElement icon={require('assets/icons/settings/statistics.png')} title={'Статистика'}/>
 | 
			
		||||
                        <SettingsElement icon={require('assets/icons/settings/printer.png')} title={'Принтеры'}/>
 | 
			
		||||
                        <SettingsElement onPress={() => {
 | 
			
		||||
                            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={'Отменить сборку'}/>
 | 
			
		||||
                    </ScrollView>
 | 
			
		||||
                    <SettingsView/>
 | 
			
		||||
                </View>
 | 
			
		||||
                <Separator/>
 | 
			
		||||
                <View style={styles.historyContainer}>
 | 
			
		||||
                    <DTitle>История операций</DTitle>
 | 
			
		||||
                    <ScrollView
 | 
			
		||||
                        showsHorizontalScrollIndicator={false}
 | 
			
		||||
                        showsVerticalScrollIndicator={false}
 | 
			
		||||
                        style={styles.historyElements}
 | 
			
		||||
                        contentContainerStyle={styles.historyElementsContainer}>
 | 
			
		||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
			
		||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
			
		||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
			
		||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
			
		||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
			
		||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
			
		||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
			
		||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
			
		||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
			
		||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
			
		||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
			
		||||
                    </ScrollView>
 | 
			
		||||
                    <TransactionsView
 | 
			
		||||
                        onRefresh={onTransactionsRefresh}
 | 
			
		||||
                        isLoading={balanceState.transactions.isLoading}
 | 
			
		||||
                        transactions={balanceState.transactions.items}
 | 
			
		||||
                        onEndReached={onTransactionsEndReached}/>
 | 
			
		||||
                </View>
 | 
			
		||||
            </View>
 | 
			
		||||
            <BottomSheetModal ref={bottomSheetModalRef}
 | 
			
		||||
                              snapPoints={snapPoints}
 | 
			
		||||
                              onDismiss={() => {
 | 
			
		||||
                                  setModalVisible(false);
 | 
			
		||||
                              }}
 | 
			
		||||
            >
 | 
			
		||||
                <View style={{flex: 1}}>
 | 
			
		||||
                    <DText>хуй</DText>
 | 
			
		||||
                </View>
 | 
			
		||||
            </BottomSheetModal>
 | 
			
		||||
 | 
			
		||||
            <View style={[styles.overlay, {display: modalVisible ? 'flex' : 'none'}]}/>
 | 
			
		||||
 | 
			
		||||
        </View>
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
@@ -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),
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ const SettingsElement: FC<SettingsElementProps> = ({icon, title, onPress}) => {
 | 
			
		||||
                <View style={styles.actionsCarouselImageWrapper}>
 | 
			
		||||
                    <Image style={styles.actionsCarouselImage} source={icon}/>
 | 
			
		||||
                </View>
 | 
			
		||||
                <DText>{title}</DText>
 | 
			
		||||
                <DText style={{textAlign: "center"}}>{title}</DText>
 | 
			
		||||
            </View>
 | 
			
		||||
        </TouchableOpacity>
 | 
			
		||||
 | 
			
		||||
@@ -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),
 | 
			
		||||
 
 | 
			
		||||
@@ -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<TransactionElementProps> = ({transaction}) => {
 | 
			
		||||
    const formatNumber = (n: number): string => n >= 0 ? `+${n}` : `${n}`;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <View style={styles.historyElementContainer}>
 | 
			
		||||
            <DText style={{
 | 
			
		||||
                fontSize: responsiveFontSize(2),
 | 
			
		||||
                fontWeight: "500"
 | 
			
		||||
            }}>{formatNumber(transaction.amount)} руб</DText>
 | 
			
		||||
            }}>{formatBalanceNumber(transaction.amount)} руб</DText>
 | 
			
		||||
            <DText style={{
 | 
			
		||||
                fontSize: responsiveFontSize(1.5),
 | 
			
		||||
                color: gray,
 | 
			
		||||
@@ -31,12 +32,13 @@ const TransactionElement: FC<TransactionElementProps> = ({transaction}) => {
 | 
			
		||||
type TransactionsViewProps = {
 | 
			
		||||
    transactions: BalanceTransaction[];
 | 
			
		||||
    onEndReached: () => void;
 | 
			
		||||
    onRefresh: () => void;
 | 
			
		||||
    isLoading: boolean;
 | 
			
		||||
}
 | 
			
		||||
const TransactionsView: FC<TransactionsViewProps> = ({transactions, onEndReached}) => {
 | 
			
		||||
    console.log('----------------------------------')
 | 
			
		||||
    console.log(JSON.stringify(transactions, null, 2))
 | 
			
		||||
const TransactionsView: FC<TransactionsViewProps> = ({transactions, isLoading, onEndReached, onRefresh}) => {
 | 
			
		||||
    return (
 | 
			
		||||
        <FlashList
 | 
			
		||||
            onRefresh={onRefresh}
 | 
			
		||||
            showsHorizontalScrollIndicator={false}
 | 
			
		||||
            showsVerticalScrollIndicator={false}
 | 
			
		||||
            ItemSeparatorComponent={flashListSeparator}
 | 
			
		||||
@@ -50,6 +52,7 @@ const TransactionsView: FC<TransactionsViewProps> = ({transactions, onEndReached
 | 
			
		||||
                    transaction={item.item}
 | 
			
		||||
                />}
 | 
			
		||||
            onEndReached={onEndReached}
 | 
			
		||||
            refreshing={isLoading}
 | 
			
		||||
        >
 | 
			
		||||
        </FlashList>
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -12,3 +12,7 @@ export type BalanceTransaction = {
 | 
			
		||||
    jsonData?: object;
 | 
			
		||||
    createdAt: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type BalanceInfo = {
 | 
			
		||||
    balance: number;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								src/utils/formatters.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/utils/formatters.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user