ebanutsya
This commit is contained in:
11
app.json
11
app.json
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"expo": {
|
||||
"plugins": [
|
||||
"react-native-keyevent-expo-config-plugin"
|
||||
],
|
||||
"name": "Assemblr",
|
||||
"slug": "Assemblr",
|
||||
"version": "1.0.0",
|
||||
@@ -21,10 +24,16 @@
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./src/assets/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
}
|
||||
},
|
||||
"package": "com.anonymous.Assemblr"
|
||||
},
|
||||
"web": {
|
||||
"favicon": "./src/assets/favicon.png"
|
||||
},
|
||||
"extra": {
|
||||
"eas": {
|
||||
"projectId": "44593e40-e504-4784-9e23-3cc90a6c1f74"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
18
eas.json
Normal file
18
eas.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"build": {
|
||||
"preview": {
|
||||
"android": {
|
||||
"buildType": "apk"
|
||||
}
|
||||
},
|
||||
"preview2": {
|
||||
"android": {
|
||||
"gradleCommand": ":app:assembleRelease"
|
||||
}
|
||||
},
|
||||
"preview3": {
|
||||
"developmentClient": true
|
||||
},
|
||||
"production": {}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
const path = require('path');
|
||||
const { getDefaultConfig } = require('expo/metro-config');
|
||||
|
||||
module.exports = {
|
||||
// Получаем конфигурацию по умолчанию от Expo
|
||||
const defaultConfig = getDefaultConfig(__dirname);
|
||||
|
||||
// Ваши настройки
|
||||
const customConfig = {
|
||||
resolver: {
|
||||
extraNodeModules: new Proxy({}, {
|
||||
get: (target, name) => path.join(process.cwd(), `src/${name}`)
|
||||
@@ -10,3 +15,16 @@ module.exports = {
|
||||
path.resolve(__dirname, 'src')
|
||||
],
|
||||
};
|
||||
|
||||
// Объединяем два конфига
|
||||
module.exports = {
|
||||
...defaultConfig,
|
||||
resolver: {
|
||||
...defaultConfig.resolver,
|
||||
...customConfig.resolver
|
||||
},
|
||||
watchFolders: [
|
||||
...defaultConfig.watchFolders,
|
||||
...customConfig.watchFolders
|
||||
]
|
||||
};
|
||||
|
||||
17594
package-lock.json
generated
17594
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@@ -4,39 +4,49 @@
|
||||
"main": "AppEntry.ts",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"web": "expo start --web"
|
||||
},
|
||||
"resolutions": {
|
||||
"react-native-reanimated": "3.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/webpack-config": "^19.0.0",
|
||||
"@gorhom/bottom-sheet": "^4",
|
||||
"@react-navigation/bottom-tabs": "^6.5.8",
|
||||
"@react-navigation/native": "^6.1.7",
|
||||
"@reduxjs/toolkit": "^1.9.5",
|
||||
"@rneui/base": "^4.0.0-rc.8",
|
||||
"@rneui/base": "^4.0.0-rc.7",
|
||||
"@rneui/themed": "^4.0.0-rc.8",
|
||||
"@shopify/flash-list": "1.4.3",
|
||||
"axios": "^1.5.0",
|
||||
"babel-plugin-module-resolver": "^5.0.0",
|
||||
"expo": "~49.0.8",
|
||||
"expo-secure-store": "~12.3.1",
|
||||
"expo-splash-screen": "~0.20.5",
|
||||
"expo-status-bar": "~1.6.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-native": "0.72.5",
|
||||
"react-native-gesture-handler": "^2.13.2",
|
||||
"react-native": "0.72.6",
|
||||
"react-native-gesture-handler": "~2.12.0",
|
||||
"react-native-keyevent": "^0.3.1",
|
||||
"react-native-keyevent-expo-config-plugin": "^1.0.49",
|
||||
"react-native-modal": "^13.0.1",
|
||||
"react-native-paper": "^5.10.6",
|
||||
"react-native-reanimated": "~3.3.0",
|
||||
"react-native-radio-buttons-group": "^3.0.5",
|
||||
"react-native-reanimated": "3.3.0",
|
||||
"react-native-responsive-dimensions": "^3.1.1",
|
||||
"react-native-responsive-fontsize": "^0.5.1",
|
||||
"react-native-safe-area-context": "4.6.3",
|
||||
"react-native-screens": "~3.22.0",
|
||||
"react-native-toast-message": "^2.1.7",
|
||||
"react-native-vector-icons": "^10.0.0",
|
||||
"react-native-web": "~0.19.6",
|
||||
"react-native-webview": "13.2.2",
|
||||
"react-redux": "^8.1.2",
|
||||
"redux": "^4.2.1"
|
||||
"redux": "^4.2.1",
|
||||
"rn-openapp": "^2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
|
||||
@@ -7,6 +7,7 @@ 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";
|
||||
|
||||
|
||||
export default function App() {
|
||||
@@ -21,6 +22,7 @@ export default function App() {
|
||||
<GestureHandlerRootView style={{flex: 1}}>
|
||||
<BottomSheetModalProvider>
|
||||
<CommonPage/>
|
||||
|
||||
</BottomSheetModalProvider>
|
||||
</GestureHandlerRootView>
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
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";
|
||||
|
||||
const apiClient = axios.create({
|
||||
baseURL: 'http://192.168.1.101:5000',
|
||||
});
|
||||
|
||||
apiClient.interceptors.request.use(async (config) => {
|
||||
const accessToken = await SecureStore.getItemAsync('access_token');
|
||||
const accessToken = await SecureStore.getItemAsync('accessToken');
|
||||
if (!config.headers) {
|
||||
config.headers = new AxiosHeaders();
|
||||
}
|
||||
@@ -14,12 +17,22 @@ apiClient.interceptors.request.use(async (config) => {
|
||||
config.headers.set('Authorization', `Bearer ${accessToken}`, true);
|
||||
}
|
||||
config.validateStatus = (status) => {
|
||||
return true
|
||||
if (status == 401) {
|
||||
SecureStore.deleteItemAsync('accessToken');
|
||||
store.dispatch(logout());
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
return config;
|
||||
}, function (error) {
|
||||
return Promise.reject(error);
|
||||
if (error.response && error.response.status === 401) {
|
||||
console.log("очко")
|
||||
|
||||
|
||||
}
|
||||
// return Promise.reject(error);
|
||||
});
|
||||
|
||||
|
||||
export default apiClient;
|
||||
|
||||
13
src/api/barcodeApi.ts
Normal file
13
src/api/barcodeApi.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import {SupplierProduct} from "../types/supplierProduct";
|
||||
import apiClient from "./apiClient";
|
||||
|
||||
const router = '/barcode';
|
||||
|
||||
const barcodeApi = {
|
||||
searchProducts: async (barcode: string): Promise<SupplierProduct[]> => {
|
||||
let response = await apiClient.get(`${router}/searchProducts?barcode=${barcode}`);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export default barcodeApi;
|
||||
16
src/api/ordersApi.ts
Normal file
16
src/api/ordersApi.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import apiClient from "./apiClient";
|
||||
import {Order} from "../types/order";
|
||||
|
||||
const router = '/orders';
|
||||
|
||||
const ordersApi = {
|
||||
getOrders: async (lastId: number) => {
|
||||
|
||||
},
|
||||
getOrdersBySupplierProduct: async (supplierProductId: number): Promise<Order[]> => {
|
||||
let response = await apiClient.get(`${router}/getBySupplierProductId?supplierProductId=${supplierProductId}`);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export default ordersApi;
|
||||
12
src/api/userApi.ts
Normal file
12
src/api/userApi.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import apiClient from "./apiClient";
|
||||
|
||||
const userApi = {
|
||||
login: async (login: string, password: string): Promise<{ accessToken: string, ok: boolean }> => {
|
||||
let response = await apiClient.post('/auth/login', {login, password});
|
||||
return response.data;
|
||||
},
|
||||
test: async () => {
|
||||
await apiClient.post('/auth/protected');
|
||||
}
|
||||
}
|
||||
export default userApi;
|
||||
9
src/components/FlashListSeparator/FlashListSeparator.tsx
Normal file
9
src/components/FlashListSeparator/FlashListSeparator.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import {FC} from "react";
|
||||
import {responsiveHeight} from "react-native-responsive-dimensions";
|
||||
import {View} from "react-native";
|
||||
|
||||
const FlashListSeparator: FC = () => {
|
||||
return (<View
|
||||
style={{flex: 1, height: responsiveHeight(2)}}></View>);
|
||||
}
|
||||
export default FlashListSeparator;
|
||||
@@ -0,0 +1,97 @@
|
||||
import React, {FC} from "react";
|
||||
import {SupplierProduct} from "../../../types/supplierProduct";
|
||||
import {Image, StyleSheet, TouchableOpacity, View} from "react-native";
|
||||
import DTitle from "../../DTitle/DTitle";
|
||||
import DText from "../../DText/DText";
|
||||
import {RFPercentage} from "react-native-responsive-fontsize";
|
||||
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
|
||||
|
||||
type Props = {
|
||||
product: SupplierProduct;
|
||||
onPress: (product: SupplierProduct) => void;
|
||||
}
|
||||
const SelectProductElement: FC<Props> = React.memo(({product, onPress}) => {
|
||||
return (
|
||||
<TouchableOpacity onPress={() => {
|
||||
onPress(product)
|
||||
}}>
|
||||
<View style={styles.container}>
|
||||
<View style={styles.imageWrapper}>
|
||||
<Image style={styles.image} source={{uri: product.thumbUrl}}/>
|
||||
</View>
|
||||
<View style={styles.description}>
|
||||
<View style={styles.descriptionContent}>
|
||||
|
||||
<DText>{product.productName}</DText>
|
||||
<DText style={{textAlign: "justify"}}>{}</DText>
|
||||
{product.supplierName &&
|
||||
<DText>Поставщик: {product.supplierName}</DText>
|
||||
}
|
||||
{product.supplierArticle &&
|
||||
<DText>Артикул: {product.supplierArticle}</DText>
|
||||
}
|
||||
{product.inBlock &&
|
||||
<DText>В блоке: {product.inBlock}</DText>
|
||||
}
|
||||
{product.shelfNumber &&
|
||||
<DText>Номер полки: {product.shelfNumber}</DText>
|
||||
}
|
||||
</View>
|
||||
|
||||
|
||||
</View>
|
||||
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
)
|
||||
})
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: "white",
|
||||
display: "flex",
|
||||
borderRadius: RFPercentage(3),
|
||||
flexDirection: "row",
|
||||
padding: RFPercentage(2),
|
||||
flex: 1,
|
||||
},
|
||||
imageWrapper: {
|
||||
width: responsiveWidth(20),
|
||||
},
|
||||
image: {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
|
||||
},
|
||||
description: {
|
||||
// backgroundColor: "red",
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
paddingLeft: responsiveWidth(3),
|
||||
gap: 0,
|
||||
|
||||
},
|
||||
title: {
|
||||
marginBottom: responsiveHeight(0.5),
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
},
|
||||
titleImage: {
|
||||
width: responsiveWidth(4),
|
||||
height: responsiveHeight(4),
|
||||
resizeMode: "center",
|
||||
marginLeft: responsiveHeight(1)
|
||||
},
|
||||
descriptionContent: {
|
||||
// backgroundColor: "green",
|
||||
flex: 1,
|
||||
},
|
||||
descriptionStatus: {
|
||||
alignSelf: "flex-end",
|
||||
// backgroundColor: "blue",
|
||||
marginRight: responsiveWidth(2),
|
||||
}
|
||||
});
|
||||
export default SelectProductElement;
|
||||
@@ -0,0 +1,59 @@
|
||||
import {FC} from "react";
|
||||
import {FlatList, StyleSheet, View} from "react-native";
|
||||
import Modal from "react-native-modal";
|
||||
import {FlashList} from "@shopify/flash-list";
|
||||
import SelectProductElement from "./SelectProductElement";
|
||||
import {SupplierProduct} from "../../../types/supplierProduct";
|
||||
import FlashListSeparator from "../../FlashListSeparator/FlashListSeparator";
|
||||
import DTitle from "../../DTitle/DTitle";
|
||||
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
|
||||
import {background} from "../../../css/colors";
|
||||
|
||||
type Props = {
|
||||
visible: boolean;
|
||||
products: SupplierProduct[];
|
||||
onSelected: (product: SupplierProduct) => void
|
||||
}
|
||||
|
||||
|
||||
const SelectProductModal: FC<Props> = ({visible, products, onSelected}) => {
|
||||
return (
|
||||
<Modal isVisible={visible}>
|
||||
<View style={styles.container}>
|
||||
<DTitle style={styles.title}>К штрихкоду привязано несколько
|
||||
товаров, выберите конкретный</DTitle>
|
||||
<FlatList
|
||||
keyboardShouldPersistTaps={"never"}
|
||||
data={products}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyExtractor={(product) => product.supplierProductId.toString()}
|
||||
renderItem={(product) => <SelectProductElement product={product.item} onPress={onSelected}/>}
|
||||
ItemSeparatorComponent={FlashListSeparator}
|
||||
>
|
||||
|
||||
</FlatList>
|
||||
</View>
|
||||
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: background,
|
||||
borderRadius: responsiveWidth(1),
|
||||
paddingHorizontal: responsiveWidth(5),
|
||||
paddingVertical: responsiveHeight(5),
|
||||
rowGap: responsiveHeight(3),
|
||||
marginVertical:responsiveHeight(10)
|
||||
},
|
||||
title: {
|
||||
textAlign: "center"
|
||||
},
|
||||
listContainer: {
|
||||
flex: 1
|
||||
}
|
||||
})
|
||||
|
||||
export default SelectProductModal;
|
||||
113
src/components/Modals/SortingModal/SortingModal.tsx
Normal file
113
src/components/Modals/SortingModal/SortingModal.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import {FC, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState} from "react";
|
||||
import {BottomSheetModal} from "@gorhom/bottom-sheet";
|
||||
import {disableDim, enableDim} from "../../../features/interface/interfaceSlice";
|
||||
import {StyleSheet, View} from "react-native";
|
||||
import BasicButton from "../../BasicButton/BasicButton";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {RFPercentage} from "react-native-responsive-fontsize";
|
||||
import RadioGroup from 'react-native-radio-buttons-group';
|
||||
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
|
||||
import {blue} from "../../../css/colors";
|
||||
|
||||
export type SortingModalHandles = {
|
||||
present: () => void;
|
||||
dismiss: () => void;
|
||||
}
|
||||
|
||||
export type SortingModalElement = {
|
||||
id: string;
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
type Props = {
|
||||
onChange: (sortingValue: string) => void;
|
||||
onClose: () => void;
|
||||
elements: SortingModalElement[];
|
||||
defaultElementId?: string;
|
||||
};
|
||||
|
||||
const createRadioButton = (element: SortingModalElement) => {
|
||||
return {
|
||||
id: element.id,
|
||||
label: element.label,
|
||||
value: element.value,
|
||||
size: RFPercentage(5),
|
||||
color: blue,
|
||||
labelStyle: {
|
||||
fontSize: responsiveWidth(3),
|
||||
fontWeight: "500" as const
|
||||
},
|
||||
borderSize: RFPercentage(0.5)
|
||||
};
|
||||
}
|
||||
|
||||
const SortingModal = forwardRef<SortingModalHandles, Props>((props: Props, ref) => {
|
||||
const {elements, onChange, onClose, defaultElementId = ""} = props;
|
||||
const snapPoints = useMemo(() => ['40%', '40%'], []);
|
||||
const dispatch = useDispatch();
|
||||
const modalRef = useRef<BottomSheetModal>(null);
|
||||
const dismiss = () => {
|
||||
if (!modalRef.current) return;
|
||||
dispatch(disableDim());
|
||||
modalRef.current.dismiss();
|
||||
onClose();
|
||||
}
|
||||
const present = () => {
|
||||
if (!modalRef.current) return;
|
||||
modalRef.current.present();
|
||||
dispatch(enableDim());
|
||||
}
|
||||
useImperativeHandle(ref, () => ({
|
||||
present: present,
|
||||
dismiss: dismiss
|
||||
}));
|
||||
const [selectedId, setSelectedId] = useState<string>(defaultElementId);
|
||||
useEffect(() => {
|
||||
onChange(selectedId);
|
||||
}, [selectedId]);
|
||||
return (
|
||||
<BottomSheetModal
|
||||
ref={modalRef}
|
||||
snapPoints={snapPoints}
|
||||
onDismiss={() => {
|
||||
dispatch(disableDim());
|
||||
}}>
|
||||
<View style={styles.container}>
|
||||
<View style={styles.content}>
|
||||
<RadioGroup selectedId={selectedId}
|
||||
onPress={setSelectedId}
|
||||
containerStyle={styles.radioButtons}
|
||||
radioButtons={elements.map(createRadioButton)}/>
|
||||
</View>
|
||||
|
||||
|
||||
<BasicButton label={"Закрыть"} style={styles.button} onPress={() => {
|
||||
dismiss();
|
||||
}}/>
|
||||
|
||||
</View>
|
||||
</BottomSheetModal>
|
||||
);
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
flex: 1,
|
||||
padding: RFPercentage(3),
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between"
|
||||
},
|
||||
radioButtons: {
|
||||
alignItems: "flex-start"
|
||||
},
|
||||
content: {},
|
||||
button: {
|
||||
marginTop: "auto"
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
export default SortingModal;
|
||||
@@ -1,26 +1,18 @@
|
||||
import {FC, useState} from "react";
|
||||
import React, {FC, useState} from "react";
|
||||
import {GestureResponderEvent, Image, StyleSheet, TouchableOpacity, View} from "react-native";
|
||||
import {RFPercentage} from "react-native-responsive-fontsize";
|
||||
import DText from "../DText/DText";
|
||||
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
|
||||
import DTitle from "../DTitle/DTitle";
|
||||
import {Order} from "../../types/order";
|
||||
|
||||
type Props = {
|
||||
onPress?: (event: GestureResponderEvent) => void
|
||||
order: Order
|
||||
|
||||
}
|
||||
const OrderCard: FC<Props> = ({onPress}) => {
|
||||
const OrderCard: FC<Props> = React.memo(({onPress, order}) => {
|
||||
|
||||
const [order, setOrder] = useState({
|
||||
article: 183658,
|
||||
imageUrl: "https://421421.selcdn.ru/denco/996956/thumbzoom/h0b41036e5dc84a88b3dd344a46ab33edt.jpg-640x640.jpg",
|
||||
orderNumber: "93757290-0104-7",
|
||||
productName: "Фигурки животных «Собачки» 258, 5-7 см., статичные / 12 шт.",
|
||||
supplierName: "simaland",
|
||||
marketplaceName: "Wildberries РЕНТА",
|
||||
sellerName: "DENCO",
|
||||
assembled: false
|
||||
});
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={() => {
|
||||
@@ -54,7 +46,7 @@ const OrderCard: FC<Props> = ({onPress}) => {
|
||||
</TouchableOpacity>
|
||||
|
||||
)
|
||||
}
|
||||
})
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: "white",
|
||||
@@ -63,6 +55,7 @@ const styles = StyleSheet.create({
|
||||
height: responsiveHeight(20),
|
||||
flexDirection: "row",
|
||||
padding: RFPercentage(2),
|
||||
flex: 1
|
||||
},
|
||||
imageWrapper: {
|
||||
width: responsiveWidth(30),
|
||||
|
||||
@@ -5,6 +5,7 @@ import {RFPercentage} from "react-native-responsive-fontsize";
|
||||
import Modal from "react-native-modal";
|
||||
import BasicButton from "../BasicButton/BasicButton";
|
||||
import DText from "../DText/DText";
|
||||
import {useDispatch} from "react-redux";
|
||||
|
||||
type Props = {
|
||||
visible: boolean
|
||||
@@ -16,14 +17,21 @@ const ScanModal: FC<Props> = ({visible, onCancelButtonPress, onChanged}) => {
|
||||
useEffect(() => {
|
||||
if (visible) inputRef.current?.focus();
|
||||
}, [visible]);
|
||||
|
||||
return (
|
||||
<Modal isVisible={visible}>
|
||||
<View style={styles.container}>
|
||||
<DText style={styles.text}>Наведите сканер на штрихкод товара или заказа</DText>
|
||||
<BasicButton onPress={onCancelButtonPress} style={styles.cancelButton} label={"Отмена"}/>
|
||||
<TextInput onEndEditing={(e) => {
|
||||
onChanged(e.nativeEvent.text);
|
||||
}} style={styles.pseudoInput} ref={inputRef}/>
|
||||
<TextInput
|
||||
onEndEditing={(e) => {
|
||||
onChanged(e.nativeEvent.text);
|
||||
}}
|
||||
style={styles.pseudoInput}
|
||||
ref={inputRef}
|
||||
autoFocus={true}
|
||||
showSoftInputOnFocus={false}
|
||||
/>
|
||||
</View>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
@@ -1,27 +1,41 @@
|
||||
import React, {FC, useRef, useState} from "react";
|
||||
import React, {FC, useEffect, useRef, useState} from "react";
|
||||
import {Image, StyleSheet, TextInput, TouchableOpacity, View} from "react-native";
|
||||
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
|
||||
import {RFPercentage} from "react-native-responsive-fontsize";
|
||||
import BasicButton from "../BasicButton/BasicButton";
|
||||
import ScanModal from "./ScanModal";
|
||||
import telegramAuthButton from "../TelegramAuthButton/TelegramAuthButton";
|
||||
import SelectProductModal from "../Modals/SelectProductModal/SelectProductModal";
|
||||
import {SupplierProduct} from "../../types/supplierProduct";
|
||||
import barcodeApi from "../../api/barcodeApi";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {openScanModal} from "../../features/scanModal/scanModalSlice";
|
||||
import {RootState} from "../../redux/store";
|
||||
|
||||
type Props = {
|
||||
onSearch: (text: string) => void;
|
||||
onSupplierProductSelected?: (supplierProduct: SupplierProduct) => void
|
||||
}
|
||||
const SearchBar: FC<Props> = ({onSearch}) => {
|
||||
const [isScanModalVisible, setIsScanModalVisible] = useState<boolean>(false);
|
||||
const SearchBar: FC<Props> = ({onSearch, onSupplierProductSelected}) => {
|
||||
// const [isScanModalVisible, setIsScanModalVisible] = useState<boolean>(false);
|
||||
// const [, setSelectProductModalVisible] = useState(false);
|
||||
const dispatch = useDispatch();
|
||||
const [searchInput, setSearchInput] = useState<string>("");
|
||||
const textInputRef = useRef<TextInput>(null);
|
||||
const [products, setProducts] = useState<SupplierProduct[]>([]);
|
||||
|
||||
const scannedData = useSelector((state: RootState) => state.scanModal.scannedData);
|
||||
useEffect(() => {
|
||||
if (!scannedData) return;
|
||||
barcodeApi.searchProducts(scannedData).then(setProducts);
|
||||
}, [scannedData]);
|
||||
|
||||
const selectProductModalVisible = products.length > 0;
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<ScanModal
|
||||
onChanged={(text) => {
|
||||
setIsScanModalVisible(false);
|
||||
onSearch(text);
|
||||
}}
|
||||
onCancelButtonPress={() => setIsScanModalVisible(false)}
|
||||
visible={isScanModalVisible}/>
|
||||
|
||||
<SelectProductModal visible={selectProductModalVisible} products={products} onSelected={(product) => {
|
||||
if (onSupplierProductSelected) onSupplierProductSelected(product);
|
||||
setProducts([]);
|
||||
}}/>
|
||||
<BasicButton onPress={() => {
|
||||
onSearch(searchInput);
|
||||
if (textInputRef.current) {
|
||||
@@ -29,7 +43,9 @@ const SearchBar: FC<Props> = ({onSearch}) => {
|
||||
}
|
||||
}} style={styles.scanButton} label={"Поиск"}/>
|
||||
<View style={styles.scanImageWrapper}>
|
||||
<TouchableOpacity onPress={() => setIsScanModalVisible(true)}>
|
||||
<TouchableOpacity onPress={() => {
|
||||
dispatch(openScanModal());
|
||||
}}>
|
||||
<Image style={styles.scanImage} source={require('assets/icons/scan.png')}/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
30
src/components/Toast/Toast.tsx
Normal file
30
src/components/Toast/Toast.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import Toast, {BaseToast, ErrorToast, ToastConfig} from 'react-native-toast-message';
|
||||
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
|
||||
|
||||
const toastConfig: ToastConfig = {
|
||||
success: (props) => (
|
||||
<BaseToast
|
||||
{...props}
|
||||
|
||||
text1Style={{fontSize: responsiveWidth(3)}}
|
||||
text2Style={{fontSize: responsiveWidth(3)}}
|
||||
style={{
|
||||
width: responsiveWidth(90),
|
||||
height: responsiveHeight(10),
|
||||
borderLeftColor: "green"
|
||||
}}
|
||||
/>),
|
||||
error: (props) => (
|
||||
<BaseToast
|
||||
{...props}
|
||||
text1Style={{fontSize: responsiveWidth(3)}}
|
||||
text2Style={{fontSize: responsiveWidth(3)}}
|
||||
style={{
|
||||
width: responsiveWidth(90),
|
||||
height: responsiveHeight(10),
|
||||
borderLeftColor: "red"
|
||||
|
||||
}}
|
||||
/>),
|
||||
}
|
||||
export default toastConfig;
|
||||
@@ -1,119 +1,32 @@
|
||||
import {createSlice, createAsyncThunk, PayloadAction, ThunkDispatch} from '@reduxjs/toolkit';
|
||||
import apiClient from 'api/apiClient';
|
||||
import {createSlice} from "@reduxjs/toolkit";
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import {AppDispatch} from "../../redux/store";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {AxiosError} from "axios";
|
||||
import {RejectedAction} from "@reduxjs/toolkit/dist/query/core/buildThunks";
|
||||
import {createApi, fetchBaseQuery} from "@reduxjs/toolkit/query/react";
|
||||
import {EndpointBuilder} from "@reduxjs/toolkit/dist/query/endpointDefinitions";
|
||||
import {useDebugValue} from "react";
|
||||
|
||||
interface AuthState {
|
||||
accessToken: string | null;
|
||||
status: 'idle' | 'loading' | 'succeeded' | 'failed';
|
||||
errorMessage: string | null;
|
||||
isAuthenticated: boolean;
|
||||
export interface AuthState {
|
||||
isAuthorized: boolean;
|
||||
accessToken?: string;
|
||||
}
|
||||
|
||||
const initialState: AuthState = {
|
||||
accessToken: null,
|
||||
status: 'idle',
|
||||
errorMessage: null,
|
||||
isAuthenticated: false
|
||||
};
|
||||
isAuthorized: false,
|
||||
accessToken: undefined,
|
||||
}
|
||||
|
||||
|
||||
export const loginUser = createAsyncThunk(
|
||||
'auth/login',
|
||||
async (credentials: { login: string; password: string }, {rejectWithValue}) => {
|
||||
try {
|
||||
const response = await apiClient.post<{ access_token: string }>('/auth/login', credentials);
|
||||
if (response.status == 401) {
|
||||
throw Error("Invalid credentials");
|
||||
}
|
||||
return response.data.access_token;
|
||||
} catch (error: any) {
|
||||
if (error instanceof AxiosError)
|
||||
return rejectWithValue('Server response error');
|
||||
else if (error instanceof Error)
|
||||
return rejectWithValue('Invalid credentials');
|
||||
return rejectWithValue('Unexpected error')
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const loadToken = createAsyncThunk<
|
||||
void,
|
||||
undefined,
|
||||
{
|
||||
dispatch: AppDispatch // Тип для dispatch
|
||||
}
|
||||
>('auth/loadToken', async (_, {dispatch}) => {
|
||||
try {
|
||||
const token = await SecureStore.getItemAsync('access_token');
|
||||
if (token) {
|
||||
dispatch(authSlice.actions.setToken(token));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Couldn't read token", error);
|
||||
}
|
||||
});
|
||||
|
||||
export const logoutUser = createAsyncThunk<
|
||||
void,
|
||||
undefined,
|
||||
{
|
||||
dispatch: AppDispatch // Тип для dispatch
|
||||
}
|
||||
>('auth/loadToken', async (_, {dispatch}) => {
|
||||
try {
|
||||
await SecureStore.deleteItemAsync('access_token');
|
||||
dispatch(authSlice.actions.logout())
|
||||
} catch (error) {
|
||||
console.error("Couldn't read token", error);
|
||||
}
|
||||
});
|
||||
|
||||
const authSlice = createSlice({
|
||||
name: 'auth',
|
||||
export const authSlice = createSlice({
|
||||
name: "auth",
|
||||
initialState,
|
||||
reducers: {
|
||||
setToken: (state, action: PayloadAction<string>) => {
|
||||
state.accessToken = action.payload;
|
||||
state.isAuthenticated = true;
|
||||
state.status = "succeeded";
|
||||
login: (state, action) => {
|
||||
state.isAuthorized = true;
|
||||
state.accessToken = action.payload.accessToken;
|
||||
},
|
||||
logout: (state) => {
|
||||
state.accessToken = null;
|
||||
state.status = "idle";
|
||||
state.isAuthenticated = false
|
||||
state.errorMessage = null;
|
||||
state.isAuthorized = false;
|
||||
state.accessToken = undefined
|
||||
}
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(loginUser.pending, (state) => {
|
||||
state.accessToken = "";
|
||||
state.status = 'loading';
|
||||
state.errorMessage = null;
|
||||
state.isAuthenticated = false;
|
||||
}).addCase(loginUser.fulfilled, (state, action: PayloadAction<string>) => {
|
||||
let accessToken = action.payload;
|
||||
|
||||
state.accessToken = accessToken;
|
||||
state.status = 'succeeded';
|
||||
state.errorMessage = null
|
||||
state.isAuthenticated = true;
|
||||
|
||||
SecureStore.setItemAsync('access_token', accessToken);
|
||||
}).addCase(loginUser.rejected, (state, action) => {
|
||||
if (action.payload)
|
||||
state.errorMessage = action.payload as string;
|
||||
state.status = "failed";
|
||||
state.isAuthenticated = false;
|
||||
state.accessToken = "";
|
||||
})
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
export const {login, logout} = authSlice.actions;
|
||||
|
||||
export default authSlice.reducer;
|
||||
export default authSlice.reducer;
|
||||
119
src/features/auth/authSlice.ts.back
Normal file
119
src/features/auth/authSlice.ts.back
Normal file
@@ -0,0 +1,119 @@
|
||||
import {createSlice, createAsyncThunk, PayloadAction, ThunkDispatch} from '@reduxjs/toolkit';
|
||||
import apiClient from 'api/apiClient';
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import {AppDispatch} from "../../redux/store";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {AxiosError} from "axios";
|
||||
import {RejectedAction} from "@reduxjs/toolkit/dist/query/core/buildThunks";
|
||||
import {createApi, fetchBaseQuery} from "@reduxjs/toolkit/query/react";
|
||||
import {EndpointBuilder} from "@reduxjs/toolkit/dist/query/endpointDefinitions";
|
||||
|
||||
interface AuthState {
|
||||
accessToken: string | null;
|
||||
status: 'idle' | 'loading' | 'succeeded' | 'failed';
|
||||
errorMessage: string | null;
|
||||
isAuthenticated: boolean;
|
||||
}
|
||||
|
||||
const initialState: AuthState = {
|
||||
accessToken: null,
|
||||
status: 'idle',
|
||||
errorMessage: null,
|
||||
isAuthenticated: false
|
||||
};
|
||||
|
||||
|
||||
export const loginUser = createAsyncThunk(
|
||||
'auth/login',
|
||||
async (credentials: { login: string; password: string }, {rejectWithValue}) => {
|
||||
try {
|
||||
const response = await apiClient.post<{ access_token: string }>('/auth/login', credentials);
|
||||
if (response.status == 401) {
|
||||
throw Error("Invalid credentials");
|
||||
}
|
||||
return response.data.access_token;
|
||||
} catch (error: any) {
|
||||
if (error instanceof AxiosError)
|
||||
return rejectWithValue('Server response error');
|
||||
else if (error instanceof Error)
|
||||
return rejectWithValue('Invalid credentials');
|
||||
return rejectWithValue('Unexpected error')
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const loadToken = createAsyncThunk<
|
||||
void,
|
||||
undefined,
|
||||
{
|
||||
dispatch: AppDispatch // Тип для dispatch
|
||||
}
|
||||
>('auth/loadToken', async (_, {dispatch}) => {
|
||||
try {
|
||||
const token = await SecureStore.getItemAsync('access_token');
|
||||
if (token) {
|
||||
dispatch(authSlice.actions.setToken(token));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Couldn't read token", error);
|
||||
}
|
||||
});
|
||||
|
||||
export const logoutUser = createAsyncThunk<
|
||||
void,
|
||||
undefined,
|
||||
{
|
||||
dispatch: AppDispatch // Тип для dispatch
|
||||
}
|
||||
>('auth/loadToken', async (_, {dispatch}) => {
|
||||
try {
|
||||
await SecureStore.deleteItemAsync('access_token');
|
||||
dispatch(authSlice.actions.logout())
|
||||
} catch (error) {
|
||||
console.error("Couldn't read token", error);
|
||||
}
|
||||
});
|
||||
|
||||
const authSlice = createSlice({
|
||||
name: 'auth',
|
||||
initialState,
|
||||
reducers: {
|
||||
setToken: (state, action: PayloadAction<string>) => {
|
||||
state.accessToken = action.payload;
|
||||
state.isAuthenticated = true;
|
||||
state.status = "succeeded";
|
||||
},
|
||||
logout: (state) => {
|
||||
state.accessToken = null;
|
||||
state.status = "idle";
|
||||
state.isAuthenticated = false
|
||||
state.errorMessage = null;
|
||||
}
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(loginUser.pending, (state) => {
|
||||
state.accessToken = "";
|
||||
state.status = 'loading';
|
||||
state.errorMessage = null;
|
||||
state.isAuthenticated = false;
|
||||
}).addCase(loginUser.fulfilled, (state, action: PayloadAction<string>) => {
|
||||
let accessToken = action.payload;
|
||||
|
||||
state.accessToken = accessToken;
|
||||
state.status = 'succeeded';
|
||||
state.errorMessage = null
|
||||
state.isAuthenticated = true;
|
||||
|
||||
SecureStore.setItemAsync('access_token', accessToken);
|
||||
}).addCase(loginUser.rejected, (state, action) => {
|
||||
if (action.payload)
|
||||
state.errorMessage = action.payload as string;
|
||||
state.status = "failed";
|
||||
state.isAuthenticated = false;
|
||||
state.accessToken = "";
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
export default authSlice.reducer;
|
||||
0
src/features/ordersList/ordersListSlice.ts
Normal file
0
src/features/ordersList/ordersListSlice.ts
Normal file
30
src/features/scanModal/scanModalSlice.ts
Normal file
30
src/features/scanModal/scanModalSlice.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import {createSlice} from "@reduxjs/toolkit";
|
||||
|
||||
export interface ScanModalState {
|
||||
isVisible: boolean;
|
||||
scannedData?: string
|
||||
}
|
||||
|
||||
const initialState: ScanModalState = {
|
||||
isVisible: false,
|
||||
scannedData: undefined
|
||||
}
|
||||
|
||||
export const scanModalSlice = createSlice({
|
||||
name: 'scanModal',
|
||||
initialState,
|
||||
reducers: {
|
||||
openScanModal: (state) => {
|
||||
state.isVisible = true
|
||||
},
|
||||
closeScanModal: (state) => {
|
||||
state.isVisible = false
|
||||
},
|
||||
setScannedData: (state, action) => {
|
||||
state.scannedData = action.payload;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const {openScanModal, closeScanModal, setScannedData} = scanModalSlice.actions;
|
||||
export default scanModalSlice.reducer;
|
||||
@@ -2,12 +2,14 @@ import {configureStore} from '@reduxjs/toolkit';
|
||||
|
||||
import authReducer from 'features/auth/authSlice';
|
||||
import interfaceReducer from 'features/interface/interfaceSlice';
|
||||
import scanModalReducer from 'features/scanModal/scanModalSlice';
|
||||
import {useDispatch} from "react-redux";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
auth: authReducer,
|
||||
interface: interfaceReducer
|
||||
interface: interfaceReducer,
|
||||
scanModal: scanModalReducer
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {Button, Text, View} from "react-native";
|
||||
import {useAppDispatch} from "../../redux/store";
|
||||
import {logoutUser, useGetPokemonByNameQuery} from "../../features/auth/authSlice";
|
||||
import * as process from "process";
|
||||
|
||||
function BarcodeScreen() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {Button, Text, View} from "react-native";
|
||||
import {useAppDispatch} from "../../redux/store";
|
||||
import {logoutUser, useGetPokemonByNameQuery} from "../../features/auth/authSlice";
|
||||
import {logoutUser, useGetPokemonByNameQuery} from "../../features/auth/authSlice.ts.back";
|
||||
import * as process from "process";
|
||||
|
||||
function BoxScreen() {
|
||||
|
||||
@@ -2,18 +2,48 @@ import {SafeAreaView, StyleSheet, View} from "react-native";
|
||||
import LoginScreen from "../LoginScreen/LoginScreen";
|
||||
import MainScreen from "../MainScreen/MainScreen";
|
||||
import SearchBar from "../../components/SearchBar/SearchBar";
|
||||
import React from "react";
|
||||
import React, {useEffect} from "react";
|
||||
import {background} from "../../css/colors";
|
||||
import {useSelector} from "react-redux";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {RootState} from "../../redux/store";
|
||||
import {login} from "../../features/auth/authSlice";
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import Toast from "react-native-toast-message";
|
||||
import toastConfig from "../../components/Toast/Toast";
|
||||
import ScanModal from "../../components/SearchBar/ScanModal";
|
||||
import {closeScanModal, setScannedData} from "../../features/scanModal/scanModalSlice";
|
||||
|
||||
function CommonPage() {
|
||||
const dim = useSelector((state: RootState) => state.interface.dim);
|
||||
const isAuthorized = useSelector((state: RootState) => state.auth.isAuthorized);
|
||||
const isScanModalVisible = useSelector((state: RootState) => state.scanModal.isVisible);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const loadSettings = async () => {
|
||||
const token = await SecureStore.getItemAsync('accessToken');
|
||||
if (!token) return;
|
||||
dispatch(login({accessToken: token}));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadSettings();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
<View style={styles.main}>
|
||||
<MainScreen/>
|
||||
{isAuthorized ? <MainScreen/> : <LoginScreen/>}
|
||||
<View style={[styles.overlay, {display: dim ? 'flex' : 'none'}]}/>
|
||||
<Toast config={toastConfig}/>
|
||||
<ScanModal visible={isScanModalVisible}
|
||||
onCancelButtonPress={() => {
|
||||
dispatch(closeScanModal());
|
||||
}}
|
||||
onChanged={text => {
|
||||
dispatch(setScannedData(text));
|
||||
dispatch(closeScanModal());
|
||||
}}/>
|
||||
</View>
|
||||
|
||||
)
|
||||
|
||||
@@ -3,27 +3,37 @@ import {StyleSheet, Text, View, ImageBackground, Linking} from 'react-native';
|
||||
import TelegramAuthButton from "components/TelegramAuthButton/TelegramAuthButton";
|
||||
import WebView from "react-native-webview";
|
||||
import InputField from "./components/InputField";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {loadToken, loginUser} from "features/auth/authSlice";
|
||||
import {AppDispatch, RootState, useAppDispatch} from "redux/store";
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import {initializeUseSelector} from "react-redux/es/hooks/useSelector";
|
||||
import HomeScreen from "../HomeScreen/HomeScreen";
|
||||
import {useAppDispatch} from "redux/store";
|
||||
import {RFPercentage, RFValue} from "react-native-responsive-fontsize";
|
||||
import {responsiveWidth} from "react-native-responsive-dimensions";
|
||||
import userApi from "../../api/userApi";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {login} from "../../features/auth/authSlice";
|
||||
import Toast from "react-native-toast-message";
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
|
||||
function LoginScreen() {
|
||||
const dispatch = useAppDispatch();
|
||||
const [login, setLogin] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const {status, errorMessage, isAuthenticated} = useSelector((state: RootState) => state.auth);
|
||||
const [loginValue, setLoginValue] = useState('dsnon');
|
||||
const [passwordValue, setPasswordValue] = useState('ochko');
|
||||
const handleLogin = async () => {
|
||||
dispatch(loginUser({login: login, password: password}));
|
||||
const response = await userApi.login(loginValue, passwordValue);
|
||||
if (!response.ok) {
|
||||
Toast.show({
|
||||
type: 'error',
|
||||
text1: 'Ошибка авторизации',
|
||||
text2: 'Неудалось войти используя указанные данные!'
|
||||
});
|
||||
setLoginValue("");
|
||||
setPasswordValue("");
|
||||
return;
|
||||
}
|
||||
await SecureStore.setItemAsync('accessToken', response.accessToken);
|
||||
dispatch(login({accessToken: response.accessToken}));
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(loadToken());
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
@@ -32,10 +42,9 @@ function LoginScreen() {
|
||||
style={styles.image}>
|
||||
<View style={styles.block}>
|
||||
<Text style={styles.authText}>Авторизация</Text>
|
||||
<InputField onChange={setLogin} placeholder={"Логин"}/>
|
||||
<InputField onChange={setPassword} secureTextEntry={false} placeholder={"Пароль"}/>
|
||||
<InputField value={loginValue} onChange={setLoginValue} placeholder={"Логин"}/>
|
||||
<InputField value={passwordValue} onChange={setPasswordValue} secureTextEntry={false} placeholder={"Пароль"}/>
|
||||
<TelegramAuthButton onPress={handleLogin}/>
|
||||
<Text style={{fontSize: 36}}>{errorMessage}</Text>
|
||||
</View>
|
||||
</ImageBackground>
|
||||
</View>}
|
||||
@@ -64,7 +73,6 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
authText: {
|
||||
color: '#2478F8',
|
||||
fontFamily: 'SF Pro Text',
|
||||
fontSize: RFPercentage(3),
|
||||
fontStyle: 'normal',
|
||||
fontWeight: '500',
|
||||
|
||||
@@ -7,13 +7,15 @@ type Props = {
|
||||
placeholder?: string;
|
||||
onChange?: (text: string) => void,
|
||||
secureTextEntry?: boolean
|
||||
value?: string
|
||||
}
|
||||
|
||||
const InputField: FC<Props> = ({placeholder, onChange, secureTextEntry = false}) => {
|
||||
const InputField: FC<Props> = ({placeholder, onChange, value, secureTextEntry = false}) => {
|
||||
return (
|
||||
<View style={styles.fieldContainer}>
|
||||
<View style={styles.textInputWrapper}>
|
||||
<TextInput
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
autoCorrect={false}
|
||||
autoCapitalize={"none"}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {Button, Text, View} from "react-native";
|
||||
import {useAppDispatch} from "../../redux/store";
|
||||
import {logoutUser, useGetPokemonByNameQuery} from "../../features/auth/authSlice";
|
||||
import {logoutUser, useGetPokemonByNameQuery} from "../../features/auth/authSlice.ts.back";
|
||||
import * as process from "process";
|
||||
|
||||
function MoneyScreen() {
|
||||
|
||||
@@ -6,6 +6,7 @@ import DTitle from "../../components/DTitle/DTitle";
|
||||
import BasicButton from "../../components/BasicButton/BasicButton";
|
||||
import Hyperlink from "../../components/Hyperlink/Hyperlink";
|
||||
import React, {useState} from "react";
|
||||
import userApi from "../../api/userApi";
|
||||
|
||||
type ArticleTextProps = {
|
||||
article: number
|
||||
@@ -48,8 +49,9 @@ function OrderScreen() {
|
||||
<View style={styles.buttonsWrapper}>
|
||||
{!order.assembled &&
|
||||
<BasicButton label={"Отметить как собранный"} onPress={() => {
|
||||
setOrder({...order, assembled: true})
|
||||
console.log(order);
|
||||
userApi.test()
|
||||
// setOrder({...order, assembled: true})
|
||||
// console.log(order);
|
||||
}}/>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,100 +1,83 @@
|
||||
import {ScrollView, StyleSheet, View} from "react-native";
|
||||
import {ActivityIndicator, FlatList, ScrollView, StyleSheet, View} from "react-native";
|
||||
import SearchBar from "../../components/SearchBar/SearchBar";
|
||||
import OrderCard from "../../components/OrderCard/OrderCard";
|
||||
import {RFPercentage} from "react-native-responsive-fontsize";
|
||||
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
|
||||
import SortingButton from "../../components/SortingButton/SortingButton";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {enableDim, disableDim} from "../../features/interface/interfaceSlice";
|
||||
import {BottomSheetModal} from "@gorhom/bottom-sheet";
|
||||
import {useMemo, useRef, useState} from "react";
|
||||
import {useEffect, useMemo, useRef, useState} from "react";
|
||||
import {RadioButton, Button} from "react-native-paper";
|
||||
import DText from "../../components/DText/DText";
|
||||
import {blue} from "../../css/colors";
|
||||
import {background, blue} from "../../css/colors";
|
||||
import DTitle from "../../components/DTitle/DTitle";
|
||||
import BasicButton from "../../components/BasicButton/BasicButton";
|
||||
import authSlice from "../../features/auth/authSlice";
|
||||
import {generateRandomOrders, Order} from "../../types/order";
|
||||
import {FlashList} from "@shopify/flash-list";
|
||||
import SortingModal, {
|
||||
SortingModalElement,
|
||||
SortingModalHandles
|
||||
} from "../../components/Modals/SortingModal/SortingModal";
|
||||
import sortingModal from "../../components/Modals/SortingModal/SortingModal";
|
||||
import flashListSeparator from "../../components/FlashListSeparator/FlashListSeparator";
|
||||
import {RootState} from "../../redux/store";
|
||||
import ordersApi from "../../api/ordersApi";
|
||||
|
||||
function OrdersScreen() {
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
||||
const snapPoints = useMemo(() => ['30%', '30%'], []);
|
||||
const [sortingValue, setSortingValue] = useState("createdOn");
|
||||
const [orders, setOrders] = useState<Order[]>([]);
|
||||
const sortingModalRef = useRef<SortingModalHandles | null>(null);
|
||||
const defaultSortingValue = 'createdOnAsc';
|
||||
const sortingModalElements: SortingModalElement[] = [
|
||||
{id: 'createdOnAsc', value: 'createdOnAsc', label: 'Дата создания по убыванию'},
|
||||
{id: 'createdOnDesc', value: 'createdOnDesc', label: 'Дата создания по возрастанию'},
|
||||
{id: 'shipmentDateAsc', value: 'shipmentDateAsc', label: 'Дата отгрузки по убыванию'},
|
||||
{id: 'shipmentDateDesc', value: 'shipmentDateDesc', label: 'Дата отгрузки по возрастанию'},
|
||||
];
|
||||
|
||||
const [sortingValue, setSortingValue] = useState(defaultSortingValue);
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<SearchBar onSearch={() => {
|
||||
}} onSupplierProductSelected={product => {
|
||||
ordersApi.getOrdersBySupplierProduct(product.supplierProductId).then(setOrders)
|
||||
}}/>
|
||||
<View style={styles.sortingButtonWrapper}>
|
||||
<SortingButton onPress={() => {
|
||||
dispatch(enableDim());
|
||||
bottomSheetModalRef.current?.present();
|
||||
|
||||
if (!sortingModalRef.current) return;
|
||||
sortingModalRef.current.present();
|
||||
}}/>
|
||||
</View>
|
||||
<View style={styles.content}>
|
||||
<ScrollView keyboardShouldPersistTaps={"never"} showsHorizontalScrollIndicator={false}
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={styles.scrollContainer}>
|
||||
<FlashList
|
||||
keyboardShouldPersistTaps={"never"}
|
||||
data={orders}
|
||||
keyExtractor={(item: Order) => item.orderNumber.toString()}
|
||||
renderItem={({item}) =>
|
||||
<OrderCard order={item} onPress={() => {
|
||||
|
||||
<OrderCard/>
|
||||
<OrderCard/>
|
||||
<OrderCard/>
|
||||
<OrderCard/>
|
||||
<OrderCard/>
|
||||
<OrderCard/>
|
||||
<OrderCard/>
|
||||
|
||||
</ScrollView>
|
||||
}}/>}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
showsVerticalScrollIndicator={true}
|
||||
onEndReachedThreshold={0.1}
|
||||
estimatedItemSize={720}
|
||||
onEndReached={() => {
|
||||
}}
|
||||
ItemSeparatorComponent={flashListSeparator}
|
||||
|
||||
/>
|
||||
<SortingModal onChange={setSortingValue}
|
||||
onClose={() => {
|
||||
console.log("Closed")
|
||||
}}
|
||||
ref={sortingModalRef}
|
||||
elements={sortingModalElements}
|
||||
defaultElementId={defaultSortingValue}
|
||||
/>
|
||||
</View>
|
||||
<BottomSheetModal
|
||||
ref={bottomSheetModalRef}
|
||||
snapPoints={snapPoints}
|
||||
onDismiss={() => {
|
||||
dispatch(disableDim());
|
||||
}}>
|
||||
<View style={styles.sortingModalContainer}>
|
||||
<View style={styles.radioButtonContainer}>
|
||||
<RadioButton
|
||||
color={blue}
|
||||
uncheckedColor={'black'}
|
||||
value={'createdOn'}
|
||||
status={sortingValue == 'createdOn' ? 'checked' : 'unchecked'}
|
||||
onPress={() => setSortingValue('createdOn')}
|
||||
|
||||
/>
|
||||
<DText>По дате создания</DText>
|
||||
</View>
|
||||
<View style={styles.radioButtonContainer}>
|
||||
<RadioButton
|
||||
color={blue}
|
||||
uncheckedColor={'black'}
|
||||
value={'shipmentDate'}
|
||||
status={sortingValue == 'shipmentDate' ? 'checked' : 'unchecked'}
|
||||
onPress={() => setSortingValue('shipmentDate')}
|
||||
/>
|
||||
<DText>По дате отгрузки</DText>
|
||||
</View>
|
||||
<View style={styles.radioButtonContainer}>
|
||||
<RadioButton
|
||||
color={blue}
|
||||
uncheckedColor={'black'}
|
||||
value={'shipmentDatee'}
|
||||
status={sortingValue == 'shipmentDatee' ? 'checked' : 'unchecked'}
|
||||
onPress={() => setSortingValue('shipmentDatee')}
|
||||
/>
|
||||
<DText>По дате дрочки</DText>
|
||||
</View>
|
||||
<View style={styles.sortingModalButton}>
|
||||
<BasicButton onPress={() => {
|
||||
bottomSheetModalRef.current?.dismiss();
|
||||
}} label={'Применть'}/>
|
||||
|
||||
</View>
|
||||
|
||||
</View>
|
||||
</BottomSheetModal>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -112,8 +95,7 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
},
|
||||
scrollContainer: {
|
||||
rowGap: responsiveHeight(2)
|
||||
|
||||
rowGap: responsiveHeight(2),
|
||||
},
|
||||
radioButtonContainer: {
|
||||
flexDirection: "row",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Button, Text, View, StyleSheet, TouchableOpacity, Image, ScrollView} from "react-native";
|
||||
import {Button, Text, View, StyleSheet, TouchableOpacity, Image, ScrollView, GestureResponderEvent} from "react-native";
|
||||
import {useAppDispatch} from "../../redux/store";
|
||||
import * as process from "process";
|
||||
import {responsiveFontSize, responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
|
||||
@@ -10,14 +10,19 @@ import {ScreenStackHeaderLeftView} from "react-native-screens";
|
||||
import Separator from "../../components/Separator/Separator";
|
||||
import {BottomSheetModal} from "@gorhom/bottom-sheet";
|
||||
import {useMemo, useRef, useState} from "react";
|
||||
import {openApp} from "rn-openapp";
|
||||
import SelectProductModal from "../../components/Modals/SelectProductModal/SelectProductModal";
|
||||
import selectProductModal from "../../components/Modals/SelectProductModal/SelectProductModal";
|
||||
|
||||
type SettingsElementProps = {
|
||||
icon: any;
|
||||
title: string;
|
||||
onPress?: (event: GestureResponderEvent) => void
|
||||
|
||||
}
|
||||
const SettingsElement: React.FC<SettingsElementProps> = ({icon, title}) => {
|
||||
const SettingsElement: React.FC<SettingsElementProps> = ({icon, title, onPress}) => {
|
||||
return (
|
||||
<TouchableOpacity>
|
||||
<TouchableOpacity onPress={onPress}>
|
||||
|
||||
<View style={styles.actionsCarouselElementContainer}>
|
||||
<View style={styles.actionsCarouselImageWrapper}>
|
||||
@@ -49,6 +54,7 @@ const HistoryElement: React.FC<HistoryElementProps> = ({cost, description, color
|
||||
}
|
||||
|
||||
function ProfileScreen() {
|
||||
|
||||
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
||||
const snapPoints = useMemo(() => ['25%', '40%'], []);
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
@@ -70,7 +76,9 @@ function ProfileScreen() {
|
||||
>
|
||||
<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={() => {
|
||||
openApp('assemblrprintingservice');
|
||||
}} icon={require('assets/icons/settings/printer.png')} title={'Принтеры'}/>
|
||||
</ScrollView>
|
||||
</View>
|
||||
<Separator/>
|
||||
@@ -107,6 +115,7 @@ function ProfileScreen() {
|
||||
</BottomSheetModal>
|
||||
|
||||
<View style={[styles.overlay, {display: modalVisible ? 'flex' : 'none'}]}/>
|
||||
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,45 @@
|
||||
export type Order = {
|
||||
id: number;
|
||||
databaseId: number;
|
||||
article: number;
|
||||
imageUrl: string;
|
||||
orderNumber: string;
|
||||
productName: string;
|
||||
supplierName: string;
|
||||
marketplaceName: string;
|
||||
sellerName: string;
|
||||
assembled: boolean;
|
||||
};
|
||||
|
||||
// Генератор случайных строк заданной длины
|
||||
function generateRandomString(length: number): string {
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Генератор случайных данных для заказа
|
||||
function generateRandomOrder(): Order {
|
||||
return {
|
||||
databaseId: Math.floor(Math.random() * 1000), // Произвольное число для databaseId
|
||||
article: Math.floor(Math.random() * 100000), // Произвольное число для article
|
||||
imageUrl: 'https://example.com/image.png', // Произвольная ссылка на изображение
|
||||
orderNumber: generateRandomString(12), // Случайная строка длиной 12 символов
|
||||
productName: 'Random Product', // Произвольное название продукта
|
||||
supplierName: 'Random Supplier', // Произвольное имя поставщика
|
||||
marketplaceName: 'Random Marketplace', // Произвольное имя маркетплейса
|
||||
sellerName: 'Random Seller', // Произвольное имя продавца
|
||||
assembled: Math.random() < 0.5, // Произвольное булевое значение (true/false)
|
||||
};
|
||||
}
|
||||
|
||||
// Генератор массива случайных заказов
|
||||
export function generateRandomOrders(count: number): Order[] {
|
||||
const orders: Order[] = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
orders.push(generateRandomOrder());
|
||||
}
|
||||
return orders;
|
||||
}
|
||||
11
src/types/supplierProduct.ts
Normal file
11
src/types/supplierProduct.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export type SupplierProduct = {
|
||||
dencoArticle: number;
|
||||
productId: number;
|
||||
supplierProductId: number;
|
||||
productName: string;
|
||||
supplierName: string;
|
||||
supplierArticle: string;
|
||||
inBlock: number;
|
||||
shelfNumber: string;
|
||||
thumbUrl: string;
|
||||
};
|
||||
Reference in New Issue
Block a user