other means

This commit is contained in:
2023-11-09 05:50:30 +03:00
parent f015090143
commit fc06a86059
32 changed files with 353 additions and 143 deletions

View File

@@ -1,7 +1,14 @@
{ {
"expo": { "expo": {
"plugins": [ "plugins": [
"react-native-keyevent-expo-config-plugin" [
"expo-build-properties",
{
"android": {
"usesCleartextTraffic": true
}
}
]
], ],
"name": "Assemblr", "name": "Assemblr",
"slug": "Assemblr", "slug": "Assemblr",
@@ -25,7 +32,10 @@
"foregroundImage": "./src/assets/adaptive-icon.png", "foregroundImage": "./src/assets/adaptive-icon.png",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
}, },
"package": "com.anonymous.Assemblr" "package": "com.anonymous.Assemblr",
"permissions": [
"INTERNET"
]
}, },
"web": { "web": {
"favicon": "./src/assets/favicon.png" "favicon": "./src/assets/favicon.png"

View File

@@ -25,6 +25,7 @@
"axios": "^1.5.0", "axios": "^1.5.0",
"babel-plugin-module-resolver": "^5.0.0", "babel-plugin-module-resolver": "^5.0.0",
"expo": "~49.0.8", "expo": "~49.0.8",
"expo-build-properties": "~0.8.3",
"expo-secure-store": "~12.3.1", "expo-secure-store": "~12.3.1",
"expo-splash-screen": "~0.20.5", "expo-splash-screen": "~0.20.5",
"expo-status-bar": "~1.6.0", "expo-status-bar": "~1.6.0",
@@ -37,13 +38,13 @@
"react-native-keyevent-expo-config-plugin": "^1.0.49", "react-native-keyevent-expo-config-plugin": "^1.0.49",
"react-native-modal": "^13.0.1", "react-native-modal": "^13.0.1",
"react-native-paper": "^5.10.6", "react-native-paper": "^5.10.6",
"react-native-progress": "^5.0.1",
"react-native-radio-buttons-group": "^3.0.5", "react-native-radio-buttons-group": "^3.0.5",
"react-native-reanimated": "3.3.0", "react-native-reanimated": "3.3.0",
"react-native-responsive-dimensions": "^3.1.1", "react-native-responsive-dimensions": "^3.1.1",
"react-native-responsive-fontsize": "^0.5.1", "react-native-responsive-fontsize": "^0.5.1",
"react-native-safe-area-context": "4.6.3", "react-native-safe-area-context": "4.6.3",
"react-native-screens": "~3.22.0", "react-native-screens": "~3.22.0",
"react-native-svg": "13.9.0",
"react-native-toast-message": "^2.1.7", "react-native-toast-message": "^2.1.7",
"react-native-vector-icons": "^10.0.0", "react-native-vector-icons": "^10.0.0",
"react-native-web": "~0.19.6", "react-native-web": "~0.19.6",

View File

@@ -5,6 +5,7 @@ import {logout} from "../features/auth/authSlice";
import {store} from "../redux/store"; import {store} from "../redux/store";
const apiClient = axios.create({ const apiClient = axios.create({
// baseURL: 'https://assemblr.denco.store',
baseURL: 'http://192.168.1.101:5000', baseURL: 'http://192.168.1.101:5000',
}); });
@@ -26,12 +27,12 @@ apiClient.interceptors.request.use(async (config) => {
return config; return config;
}, function (error) { }, function (error) {
console.log("очко")
if (error.response && error.response.status === 401) { if (error.response && error.response.status === 401) {
console.log("очко") console.log("очко")
} }
// return Promise.reject(error);
}); });

View File

@@ -17,6 +17,10 @@ const assemblyApi = {
let response = await apiClient.post(`${router}/close`, {assemblyId}); let response = await apiClient.post(`${router}/close`, {assemblyId});
return response.data; return response.data;
}, },
cancel: async (): Promise<{ ok: boolean, message: string }> => {
let response = await apiClient.post(`${router}/cancel`);
return response.data;
},
getActive: async (): Promise<Assembly> => { getActive: async (): Promise<Assembly> => {
let response = await apiClient.get(`${router}/getActive`); let response = await apiClient.get(`${router}/getActive`);
return response.data; return response.data;

View File

@@ -1,10 +1,10 @@
import {SupplierProduct} from "../types/supplierProduct";
import apiClient from "./apiClient"; import apiClient from "./apiClient";
import {Product} from "../types/product";
const router = '/barcode'; const router = '/barcode';
const barcodeApi = { const barcodeApi = {
searchProducts: async (barcode: string): Promise<SupplierProduct[]> => { searchProducts: async (barcode: string): Promise<Product[]> => {
let response = await apiClient.get(`${router}/searchProducts?barcode=${barcode}`); let response = await apiClient.get(`${router}/searchProducts?barcode=${barcode}`);
return response.data; return response.data;
} }

12
src/api/generalApi.ts Normal file
View File

@@ -0,0 +1,12 @@
import apiClient from "./apiClient";
import {ShippingWarehouse} from "../types/shippingWarehouse";
const router = '/general';
const generalApi = {
getShippingWarehouses: async (): Promise<ShippingWarehouse[]> => {
let response = await apiClient.get(`${router}/getShippingWarehouses`);
return response.data;
},
}
export default generalApi;

View File

@@ -1,22 +1,24 @@
import apiClient from "./apiClient"; import apiClient from "./apiClient";
import {Order} from "../types/order"; import {Order} from "../types/order";
import * as inspector from "inspector";
const router = '/orders'; const router = '/orders';
const ordersApi = { const ordersApi = {
getOrders: async (page: number, orderBy: string, desc: boolean, shipmentDate: string, status: number): Promise<Order[]> => { getOrders: async (page: number, orderBy: string, desc: boolean, shipmentDate: string, status: number, shipmentWarehouseId: number): Promise<Order[]> => {
const params = { const params = {
page: page, page: page,
orderBy: orderBy, orderBy: orderBy,
desc: Number(desc), // Преобразование boolean в Number (0 или 1) desc: Number(desc), // Преобразование boolean в Number (0 или 1)
status: status, status: status,
shipmentDate: shipmentDate shipmentDate: shipmentDate,
shipmentWarehouseId: shipmentWarehouseId
}; };
let response = await apiClient.get(`${router}/getOrders`, {params}) let response = await apiClient.get(`${router}/getOrders`, {params})
return response.data; return response.data;
}, },
getOrdersBySupplierProduct: async (supplierProductId: number): Promise<Order[]> => { getOrdersByProduct: async (productId: number): Promise<Order[]> => {
let response = await apiClient.get(`${router}/getBySupplierProductId?supplierProductId=${supplierProductId}`); let response = await apiClient.get(`${router}/getByProductId?productId=${productId}`);
return response.data; return response.data;
}, },
getOrderById: async (orderId: number): Promise<Order> => { getOrderById: async (orderId: number): Promise<Order> => {

View File

@@ -5,8 +5,5 @@ const userApi = {
let response = await apiClient.post('/auth/login', {login, password}); let response = await apiClient.post('/auth/login', {login, password});
return response.data; return response.data;
}, },
test: async () => {
await apiClient.post('/auth/protected');
}
} }
export default userApi; export default userApi;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -1,31 +1,21 @@
import {FC} from "react"; import React, {FC, useState} from "react";
import {Props} from "react-native-paper";
import {StyleSheet, View} from "react-native"; import {StyleSheet, View} from "react-native";
import {BottomSheetModalProvider} from "@gorhom/bottom-sheet";
import {useSelector} from "react-redux"; import {useSelector} from "react-redux";
import {RootState} from "../../../redux/store"; import {RootState} from "../../../redux/store";
import Modal from "react-native-modal"; import Modal from "react-native-modal";
import {background, blue} from "../../../css/colors"; import {background} from "../../../css/colors";
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions"; import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
import * as Progress from 'react-native-progress';
import DTitle from "../../DTitle/DTitle"; import DTitle from "../../DTitle/DTitle";
import {RFPercentage} from "react-native-responsive-fontsize";
const LoadingModal: FC = () => { const LoadingModal: FC = () => {
const isVisible = useSelector((state: RootState) => state.loadingModal.isVisible); const isVisible = useSelector((state: RootState) => state.loadingModal.isVisible);
const loadingText = useSelector((state: RootState) => state.loadingModal.loadingText); const loadingText = useSelector((state: RootState) => state.loadingModal.loadingText);
const [ch, sCh] = useState(false);
return ( return (
<Modal isVisible={isVisible}> <Modal isVisible={isVisible}>
<View style={styles.container}> <View style={styles.container}>
<DTitle style={{textAlign: "center"}}>{loadingText}</DTitle> <DTitle style={{textAlign: "center"}}>{loadingText}</DTitle>
<View style={styles.progressBarWrapper}> <View style={styles.progressBarWrapper}>
<Progress.Circle size={RFPercentage(20)}
color={blue}
indeterminate={true}
style={styles.progressBar}
borderWidth={responsiveWidth(1)}
/>
</View> </View>
</View> </View>

View File

@@ -5,10 +5,11 @@ import DTitle from "../../DTitle/DTitle";
import DText from "../../DText/DText"; import DText from "../../DText/DText";
import {RFPercentage} from "react-native-responsive-fontsize"; import {RFPercentage} from "react-native-responsive-fontsize";
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions"; import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
import {Product} from "../../../types/product";
type Props = { type Props = {
product: SupplierProduct; product: Product;
onPress: (product: SupplierProduct) => void; onPress: (product: Product) => void;
} }
const SelectProductElement: FC<Props> = React.memo(({product, onPress}) => { const SelectProductElement: FC<Props> = React.memo(({product, onPress}) => {
return ( return (
@@ -24,18 +25,7 @@ const SelectProductElement: FC<Props> = React.memo(({product, onPress}) => {
<DText>{product.productName}</DText> <DText>{product.productName}</DText>
<DText style={{textAlign: "justify"}}>{}</DText> <DText style={{textAlign: "justify"}}>{}</DText>
{product.supplierName && <DText>Артикул DENCO: {product.dencoArticle}</DText>
<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 File

@@ -8,11 +8,12 @@ import FlashListSeparator from "../../FlashListSeparator/FlashListSeparator";
import DTitle from "../../DTitle/DTitle"; import DTitle from "../../DTitle/DTitle";
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions"; import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
import {background} from "../../../css/colors"; import {background} from "../../../css/colors";
import {Product} from "../../../types/product";
type Props = { type Props = {
visible: boolean; visible: boolean;
products: SupplierProduct[]; products: Product[];
onSelected: (product: SupplierProduct) => void onSelected: (product: Product) => void
} }
@@ -27,7 +28,7 @@ const SelectProductModal: FC<Props> = ({visible, products, onSelected}) => {
data={products} data={products}
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
keyExtractor={(product) => product.supplierProductId.toString()} keyExtractor={(product) => product.productId.toString()}
renderItem={(product) => <SelectProductElement product={product.item} onPress={onSelected}/>} renderItem={(product) => <SelectProductElement product={product.item} onPress={onSelected}/>}
ItemSeparatorComponent={FlashListSeparator} ItemSeparatorComponent={FlashListSeparator}
> >

View File

@@ -13,12 +13,11 @@ import DateTimePicker from '@react-native-community/datetimepicker';
import {RootState} from "../../../redux/store"; import {RootState} from "../../../redux/store";
import { import {
closeOrdersFilterModal, closeOrdersFilterModal,
OrderStatus,
orderStatuses, orderStatuses,
setDesc, setDesc,
setOrderBy, setShipmentDate, setStatus setOrderBy, setShipmentDate, setShippingWarehouse, setStatus
} from "../../../features/ordersFilter/ordersFilterSlice"; } from "../../../features/ordersFilter/ordersFilterSlice";
import {retry} from "@reduxjs/toolkit/query"; import ShippingWarehouseSelect from "../../ShippingWarehouseSelect/ShippingWarehouseSelect";
export type SortingModalHandles = { export type SortingModalHandles = {
present: () => void; present: () => void;
@@ -49,6 +48,8 @@ const createRadioButton = (element: SortingModalElement) => {
const SortingModal = () => { const SortingModal = () => {
const state = useSelector((state: RootState) => state.ordersFilter); const state = useSelector((state: RootState) => state.ordersFilter);
const shipmentWarehouseSelectorState = useSelector((state: RootState) => state.shippingWarehouseSelect);
const elements = [ const elements = [
{id: 'createdOnAsc', value: 'createdOnAsc', label: 'Дата создания по возрастанию'}, {id: 'createdOnAsc', value: 'createdOnAsc', label: 'Дата создания по возрастанию'},
{id: 'createdOnDesc', value: 'createdOnDesc', label: 'Дата создания по убыванию'}, {id: 'createdOnDesc', value: 'createdOnDesc', label: 'Дата создания по убыванию'},
@@ -73,6 +74,9 @@ const SortingModal = () => {
if (state.isVisible) present(); if (state.isVisible) present();
else dismiss(); else dismiss();
}, [state.isVisible]); }, [state.isVisible]);
useEffect(() => {
dispatch(setShippingWarehouse(shipmentWarehouseSelectorState.selectedShippingWarehouse));
}, [shipmentWarehouseSelectorState.selectedShippingWarehouse]);
return ( return (
<BottomSheetModal <BottomSheetModal
ref={modalRef} ref={modalRef}
@@ -99,15 +103,11 @@ const SortingModal = () => {
radioButtons={elements.map(createRadioButton)}/> radioButtons={elements.map(createRadioButton)}/>
<View style={{ <View style={styles.selectors}>
borderWidth: responsiveWidth(0.1), <View style={styles.selector}>
borderRadius: responsiveWidth(1)
}}>
<Picker selectedValue={state.status} <Picker selectedValue={state.status}
onValueChange={(value, event) => dispatch(setStatus(value))}> onValueChange={(value, event) => dispatch(setStatus(value))}>
{orderStatuses.map((status) => { {orderStatuses.map((status) => {
return ( return (
<Picker.Item <Picker.Item
key={status.key} key={status.key}
@@ -119,6 +119,10 @@ const SortingModal = () => {
})} })}
</Picker> </Picker>
</View> </View>
<ShippingWarehouseSelect/>
</View>
<BasicButton onPress={() => setShowShipmentPicker(oldValue => !oldValue)} <BasicButton onPress={() => setShowShipmentPicker(oldValue => !oldValue)}
label={"Выбрать дату отгрузки"}/> label={"Выбрать дату отгрузки"}/>
{showShipmentPicker && {showShipmentPicker &&
@@ -162,7 +166,13 @@ const styles = StyleSheet.create({
marginTop: "auto" marginTop: "auto"
}, },
selectors: {
rowGap: responsiveHeight(1)
},
selector: {
borderWidth: responsiveWidth(0.1),
borderRadius: responsiveWidth(1)
}
}); });

View File

@@ -14,11 +14,7 @@ const ScanModal: FC = () => {
const visible = useSelector((state: RootState) => state.scanModal.isVisible); const visible = useSelector((state: RootState) => state.scanModal.isVisible);
const dispatch = useDispatch(); const dispatch = useDispatch();
useEffect(() => { useEffect(() => {
// if (visible) inputRef.current?.focus(); if (visible) inputRef.current?.focus();
if (visible){
dispatch(setScannedData('4750735280715'));
dispatch(closeScanModal())
}
}, [visible]); }, [visible]);
return ( return (
<Modal isVisible={visible}> <Modal isVisible={visible}>

View File

@@ -9,17 +9,18 @@ import barcodeApi from "../../api/barcodeApi";
import {useDispatch, useSelector} from "react-redux"; import {useDispatch, useSelector} from "react-redux";
import {openScanModal, setScannedData} from "../../features/scanModal/scanModalSlice"; import {openScanModal, setScannedData} from "../../features/scanModal/scanModalSlice";
import {RootState} from "../../redux/store"; import {RootState} from "../../redux/store";
import {Product} from "../../types/product";
type Props = { type Props = {
onSearch?: (text: string) => void; onSearch?: (text: string) => void;
onSupplierProductSelected?: (supplierProduct: SupplierProduct) => void onProductSelected?: (product: Product) => void
} }
const SearchBar: FC<Props> = ({onSearch, onSupplierProductSelected}) => { const SearchBar: FC<Props> = ({onSearch, onProductSelected}) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [searchInput, setSearchInput] = useState<string>(""); const [searchInput, setSearchInput] = useState<string>("");
const textInputRef = useRef<TextInput>(null); const textInputRef = useRef<TextInput>(null);
const [products, setProducts] = useState<SupplierProduct[]>([]); const [products, setProducts] = useState<Product[]>([]);
const scannedData = useSelector((state: RootState) => state.scanModal.scannedData); const scannedData = useSelector((state: RootState) => state.scanModal.scannedData);
useEffect(() => { useEffect(() => {
@@ -28,11 +29,19 @@ const SearchBar: FC<Props> = ({onSearch, onSupplierProductSelected}) => {
setProducts(response) setProducts(response)
}); });
}, [scannedData]); }, [scannedData]);
const selectProductModalVisible = products.length > 0; useEffect(() => {
if (products.length == 0 || products.length > 1 || !onProductSelected) return;
onProductSelected(products[0]);
setProducts([]);
dispatch(setScannedData(undefined));
}, [products]);
const selectProductModalVisible = products.length > 1;
return ( return (
<View style={styles.container}> <View style={styles.container}>
<SelectProductModal visible={selectProductModalVisible} products={products} onSelected={(product) => { <SelectProductModal visible={selectProductModalVisible}
if (onSupplierProductSelected) onSupplierProductSelected(product); products={products}
onSelected={(product) => {
if (onProductSelected) onProductSelected(product);
setProducts([]); setProducts([]);
dispatch(setScannedData(undefined)); dispatch(setScannedData(undefined));
}}/> }}/>

View File

@@ -0,0 +1,44 @@
import {FC, useEffect} from "react";
import {useDispatch, useSelector} from "react-redux";
import {RootState} from "../../redux/store";
import {Picker} from "@react-native-picker/picker";
import generalApi from "../../api/generalApi";
import {
initializeShippingWarehouseSelect,
selectShippingWarehouse
} from "../../features/shippingWarehouseSelect/shippingWarehouseSelectSlice";
import {responsiveWidth} from "react-native-responsive-dimensions";
import {View} from "react-native";
const ShippingWarehouseSelect: FC = () => {
const state = useSelector((state: RootState) => state.shippingWarehouseSelect);
const dispatch = useDispatch();
useEffect(() => {
if (state.initialized) return;
generalApi.getShippingWarehouses().then(shippingWarehouses =>
dispatch(initializeShippingWarehouseSelect(shippingWarehouses)))
}, []);
return (
<View style={{
borderWidth: responsiveWidth(0.1),
borderRadius: responsiveWidth(1)
}}>
<Picker
selectedValue={state.selectedShippingWarehouse?.id}
onValueChange={(value, event) => dispatch(selectShippingWarehouse({shippingWarehouseId: value}))}
>
{state.shippingWarehouses.map(shippingWarehouse => (
<Picker.Item
key={shippingWarehouse.id}
label={shippingWarehouse.name}
value={shippingWarehouse.id}
style={{fontSize: responsiveWidth(3)}}
/>
))}
</Picker>
</View>
)
}
export default ShippingWarehouseSelect

View File

@@ -25,6 +25,7 @@ export const assembly = createSlice({
state.initialOrder = action.payload; state.initialOrder = action.payload;
state.order = action.payload; state.order = action.payload;
state.selectedProductId = 0; state.selectedProductId = 0;
state.localState = ASSEMBLY_STATE.NOT_STARTED;
}, },
setAssembly: (state, action: PayloadAction<Assembly>) => { setAssembly: (state, action: PayloadAction<Assembly>) => {
state.assembly = action.payload; state.assembly = action.payload;
@@ -80,11 +81,20 @@ export const assembly = createSlice({
} }
}); });
state.selectedProduct = state.order.products.find(product => product.databaseId == action.payload.orderProductId); state.selectedProduct = state.order.products.find(product => product.databaseId == action.payload.orderProductId);
},
setLocalState: (state, action: PayloadAction<ASSEMBLY_STATE>) => {
state.localState = action.payload;
}, },
selectProduct: (state, action: PayloadAction<number>) => { selectProduct: (state, action: PayloadAction<number>) => {
if (!state.order) return; if (!state.order) return;
state.selectedProduct = state.order.products[action.payload]; state.selectedProduct = state.order.products[action.payload];
},
reset: (state) => {
state.assembly = undefined;
state.order = undefined;
state.localState = ASSEMBLY_STATE.NOT_STARTED
state.selectedProductId = undefined;
state.selectedProduct = undefined;
} }
} }
}) })
@@ -96,6 +106,8 @@ export const {
setAssembly, setAssembly,
startAssembly, startAssembly,
endAssembly, endAssembly,
confirmAssembly confirmAssembly,
setLocalState,
reset
} = assembly.actions; } = assembly.actions;
export default assembly.reducer; export default assembly.reducer;

View File

@@ -1,4 +1,6 @@
import {createSlice, PayloadAction} from "@reduxjs/toolkit"; import {createSlice, PayloadAction} from "@reduxjs/toolkit";
import {ShippingWarehouse} from "../../types/shippingWarehouse";
import ShippingWarehouseSelect from "../../components/ShippingWarehouseSelect/ShippingWarehouseSelect";
export enum OrderStatus { export enum OrderStatus {
ALL = -1, ALL = -1,
@@ -42,15 +44,20 @@ export interface OrdersFilterState {
status: OrderStatus; status: OrderStatus;
shipmentDate: string; shipmentDate: string;
page: number; page: number;
shippingWarehouse: ShippingWarehouse
} }
const initialState: OrdersFilterState = { const initialState: OrdersFilterState = {
isVisible: false, isVisible: false,
orderBy: "createdOn", orderBy: "shipmentDate",
desc: true, desc: true,
shipmentDate: (new Date()).toISOString(), shipmentDate: (new Date()).toISOString(),
status: OrderStatus.AWAITING_PACKAGING, status: OrderStatus.AWAITING_PACKAGING,
page: 0 page: 0,
shippingWarehouse: {
id: -1,
name: 'Все склады отгрузки'
}
} }
export const ordersFilterSlice = createSlice({ export const ordersFilterSlice = createSlice({
@@ -75,14 +82,18 @@ export const ordersFilterSlice = createSlice({
setShipmentDate: (state, action: PayloadAction<string>) => { setShipmentDate: (state, action: PayloadAction<string>) => {
state.shipmentDate = action.payload; state.shipmentDate = action.payload;
}, },
setShippingWarehouse: (state, action: PayloadAction<ShippingWarehouse>) => {
state.shippingWarehouse = action.payload;
},
setPage: (state, action: PayloadAction<number>) => { setPage: (state, action: PayloadAction<number>) => {
console.log(action)
state.page = action.payload; state.page = action.payload;
}, },
nextPage: (state) => { nextPage: (state) => {
state.page = state.page + 1 state.page = state.page + 1
}, },
refreshPagination: (state) => { refreshPagination: (state) => {
state.page = 0 state.page = -1
} }
} }
}) })
@@ -95,6 +106,8 @@ export const {
setOrderBy, setOrderBy,
setDesc, setDesc,
setStatus, setStatus,
setShipmentDate setShipmentDate,
setPage,
setShippingWarehouse
} = ordersFilterSlice.actions; } = ordersFilterSlice.actions;
export default ordersFilterSlice.reducer; export default ordersFilterSlice.reducer;

View File

@@ -0,0 +1,38 @@
import {createSlice, PayloadAction} from "@reduxjs/toolkit";
import {ShippingWarehouse} from "../../types/shippingWarehouse";
export interface ShippingWarehouseSelectState {
shippingWarehouses: ShippingWarehouse[]
selectedShippingWarehouse: ShippingWarehouse
initialized: boolean
}
const initialState: ShippingWarehouseSelectState = {
shippingWarehouses: [],
selectedShippingWarehouse: {
id: -1,
name: "Все склады отгрузки"
},
initialized: false
}
export const shippingWarehouseSelect = createSlice({
name: 'shippingWarehouse',
initialState,
reducers: {
initializeShippingWarehouseSelect: (state, action: PayloadAction<ShippingWarehouse[]>) => {
state.shippingWarehouses = action.payload;
state.initialized = true;
if (state.shippingWarehouses.length > 0) state.selectedShippingWarehouse = state.shippingWarehouses[0];
},
selectShippingWarehouse: (state, action: PayloadAction<{ shippingWarehouseId: number }>) => {
let selectedWarehouse = state.shippingWarehouses.find(wh => wh.id == action.payload.shippingWarehouseId);
if (!selectedWarehouse) return;
state.selectedShippingWarehouse = selectedWarehouse;
}
}
})
export const {initializeShippingWarehouseSelect, selectShippingWarehouse} = shippingWarehouseSelect.actions;
export default shippingWarehouseSelect.reducer;

View File

@@ -9,6 +9,7 @@ import assemblyReducer from 'features/assembly/assemblySlice';
import printingReducer from 'features/printing/printingSlice'; import printingReducer from 'features/printing/printingSlice';
import reprintModalReducer from 'features/reprintModal/reprintModalSlice'; import reprintModalReducer from 'features/reprintModal/reprintModalSlice';
import ordersFilterReducer from 'features/ordersFilter/ordersFilterSlice'; import ordersFilterReducer from 'features/ordersFilter/ordersFilterSlice';
import shippingWarehouseSelectReducer from 'features/shippingWarehouseSelect/shippingWarehouseSelectSlice';
import {useDispatch} from "react-redux"; import {useDispatch} from "react-redux";
export const store = configureStore({ export const store = configureStore({
@@ -21,7 +22,8 @@ export const store = configureStore({
printing: printingReducer, printing: printingReducer,
reprintModal: reprintModalReducer, reprintModal: reprintModalReducer,
imageZoomModal: imageZoomModalReducer, imageZoomModal: imageZoomModalReducer,
ordersFilter: ordersFilterReducer ordersFilter: ordersFilterReducer,
shippingWarehouseSelect: shippingWarehouseSelectReducer
}, },
}); });

View File

@@ -14,6 +14,7 @@ import flashListSeparator from "../../components/FlashListSeparator/FlashListSep
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
import {openScanModal} from "../../features/scanModal/scanModalSlice"; import {openScanModal} from "../../features/scanModal/scanModalSlice";
import ordersApi from "../../api/ordersApi"; import ordersApi from "../../api/ordersApi";
import DTitle from "../../components/DTitle/DTitle";
function BarcodeScreen() { function BarcodeScreen() {
const dispatch = useDispatch(); const dispatch = useDispatch();
@@ -26,12 +27,11 @@ function BarcodeScreen() {
}, []); }, []);
return ( return (
<View style={styles.container}> <View style={styles.container}>
<SearchBar onSupplierProductSelected={product => { <SearchBar onProductSelected={product => {
ordersApi.getOrdersBySupplierProduct(product.supplierProductId).then(setOrders) ordersApi.getOrdersByProduct(product.productId).then(setOrders)
}}/> }}/>
<View style={styles.content}> <View style={styles.content}>
<FlashList {orders.length > 0 ? <FlashList
keyboardShouldPersistTaps={"never"} keyboardShouldPersistTaps={"never"}
data={orders} data={orders}
keyExtractor={(item: Order) => item.orderNumber.toString()} keyExtractor={(item: Order) => item.orderNumber.toString()}
@@ -45,8 +45,14 @@ function BarcodeScreen() {
onEndReachedThreshold={0.1} onEndReachedThreshold={0.1}
estimatedItemSize={720} estimatedItemSize={720}
ItemSeparatorComponent={flashListSeparator} ItemSeparatorComponent={flashListSeparator}
/> :
<View style={{justifyContent: "center", flex: 1}}>
<DTitle style={{
textAlign: "center",
}}>Нет заказов по данному
товару</DTitle>
</View>}
/>
</View> </View>
</View> </View>
) )

View File

@@ -15,10 +15,12 @@ import {closeScanModal, setScannedData} from "../../features/scanModal/scanModal
import LoadingModal from "../../components/Modals/LoadingModal/LoadingModal"; import LoadingModal from "../../components/Modals/LoadingModal/LoadingModal";
import ReprintModal from "../../components/Modals/ReprintModal/ReprintModal"; import ReprintModal from "../../components/Modals/ReprintModal/ReprintModal";
import assemblyApi from "../../api/assemblyApi"; import assemblyApi from "../../api/assemblyApi";
import {assembly, setAssembly, setOrder} from "../../features/assembly/assemblySlice"; import {assembly, setAssembly, setLocalState, setOrder} from "../../features/assembly/assemblySlice";
import ordersApi from "../../api/ordersApi"; import ordersApi from "../../api/ordersApi";
import ImageZoomModal from "../../components/Modals/ImageZoomModal/ImageZoomModal"; import ImageZoomModal from "../../components/Modals/ImageZoomModal/ImageZoomModal";
import SortingModal from "../../components/Modals/SortingModal/SortingModal"; import SortingModal from "../../components/Modals/SortingModal/SortingModal";
import {setLoadingText} from "../../features/loadingModal/loadingModalSlice";
import {ASSEMBLY_STATE} from "../../types/assembly";
function CommonPage() { function CommonPage() {
const dim = useSelector((state: RootState) => state.interface.dim); const dim = useSelector((state: RootState) => state.interface.dim);
@@ -31,6 +33,7 @@ function CommonPage() {
dispatch(login({accessToken: token})); dispatch(login({accessToken: token}));
return true; return true;
} }
const loadAssembly = async () => { const loadAssembly = async () => {
assemblyApi.hasActive().then(({has}) => { assemblyApi.hasActive().then(({has}) => {
if (!has) return; if (!has) return;
@@ -38,6 +41,7 @@ function CommonPage() {
ordersApi.getOrderById(assembly.orderId).then(order => { ordersApi.getOrderById(assembly.orderId).then(order => {
dispatch(setAssembly(assembly)); dispatch(setAssembly(assembly));
dispatch(setOrder(order)); dispatch(setOrder(order));
dispatch(setLocalState(assembly.state as ASSEMBLY_STATE));
}) })
}) })
}) })

View File

@@ -14,8 +14,8 @@ import * as SecureStore from 'expo-secure-store';
function LoginScreen() { function LoginScreen() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [loginValue, setLoginValue] = useState('dsnon'); const [loginValue, setLoginValue] = useState('');
const [passwordValue, setPasswordValue] = useState('ochko'); const [passwordValue, setPasswordValue] = useState('');
const handleLogin = async () => { const handleLogin = async () => {
const response = await userApi.login(loginValue, passwordValue); const response = await userApi.login(loginValue, passwordValue);
if (!response.ok) { if (!response.ok) {

View File

@@ -1,14 +1,10 @@
import {View, Text, Image, StyleSheet, Button, TouchableOpacity} from "react-native"; import {View, Image, StyleSheet, TouchableOpacity} from "react-native";
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions"; import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
import DText from "../../components/DText/DText"; import DText from "../../components/DText/DText";
import {RFPercentage} from "react-native-responsive-fontsize"; import {RFPercentage} from "react-native-responsive-fontsize";
import DTitle from "../../components/DTitle/DTitle"; import DTitle from "../../components/DTitle/DTitle";
import BasicButton from "../../components/BasicButton/BasicButton"; import BasicButton from "../../components/BasicButton/BasicButton";
import Hyperlink from "../../components/Hyperlink/Hyperlink";
import React, {FC, useEffect, useState} from "react"; import React, {FC, useEffect, useState} from "react";
import userApi from "../../api/userApi";
import {StatusBar} from "expo-status-bar";
import * as Progress from 'react-native-progress';
import {useDispatch, useSelector} from "react-redux"; import {useDispatch, useSelector} from "react-redux";
import {RootState} from "../../redux/store"; import {RootState} from "../../redux/store";
import {closeLoadingModal, openLoadingModal, setLoadingText} from "../../features/loadingModal/loadingModalSlice"; import {closeLoadingModal, openLoadingModal, setLoadingText} from "../../features/loadingModal/loadingModalSlice";
@@ -182,13 +178,14 @@ const OrderScreen: FC<OrderScreenProps> = ({order}) => {
} }
const printLabel = () => { const printLabel = () => {
if (!order) return; if (!order) return;
let printer = printingService.getInstance().getPrinter(order.baseMarketplace);
dispatch(setLoadingText('Идет печать этикетки...')) dispatch(setLoadingText('Идет печать этикетки...'))
dispatch(openLoadingModal()) dispatch(openLoadingModal())
printingApi.getLabel(order.databaseId).then(pdfData => { printingApi.getLabel(order.databaseId).then(pdfData => {
printingService.getInstance().printPdf('wildberries', pdfData).then((response) => { printingService.getInstance().printPdf(printer, pdfData).then((response) => {
dispatch(closeLoadingModal()); dispatch(closeLoadingModal());
if (response) return; if (response) return;
dispatch(setPrinterName({printerName: 'wildberries'})); dispatch(setPrinterName({printerName: printer}));
dispatch(openReprintModal()); dispatch(openReprintModal());
}); });
}) })

View File

@@ -4,37 +4,63 @@ import ordersApi from "../../api/ordersApi";
import {setOrder} from "../../features/assembly/assemblySlice"; import {setOrder} from "../../features/assembly/assemblySlice";
import {RootState} from "../../redux/store"; import {RootState} from "../../redux/store";
import {useDispatch, useSelector} from "react-redux"; import {useDispatch, useSelector} from "react-redux";
import {refreshPagination} from "../../features/ordersFilter/ordersFilterSlice"; import {
refreshPagination,
setOrderBy,
setPage,
setShippingWarehouse
} from "../../features/ordersFilter/ordersFilterSlice";
const useOrders = () => { const useOrders = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [orders, setOrders] = useState<Order[]>([]); const [orders, setOrders] = useState<Order[]>([]);
const [isInitialized, setIsInitialized] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false);
const filterState = useSelector((state: RootState) => state.ordersFilter); const filterState = useSelector((state: RootState) => state.ordersFilter);
const fetchOrders = async (): Promise<Order[]> => {
return ordersApi.getOrders(
filterState.page,
filterState.orderBy,
filterState.desc,
filterState.shipmentDate,
filterState.status,
filterState.shippingWarehouse.id
);
}
const appendOrders = (newOrders: Order[]) => {
setOrders([...newOrders, ...orders]);
}
const refresh = () => { const refresh = () => {
if (filterState.page == 0) return;
setIsRefreshing(true);
setOrders([]); setOrders([]);
dispatch(refreshPagination()); dispatch(setPage(0));
fetchOrders().then(setOrders);
} }
useEffect(() => { useEffect(() => {
if (filterState.isVisible) return; if (!isInitialized || filterState.isVisible) return;
fetchOrders().then(appendOrders);
ordersApi.getOrders(filterState.page, filterState.orderBy, filterState.desc, filterState.shipmentDate, filterState.status).then(response => {
setOrders(oldOrders => [...oldOrders, ...response]);
setIsRefreshing(false);
});
}, [filterState.page]); }, [filterState.page]);
useEffect(() => { useEffect(() => {
if (filterState.isVisible) return; if (!isInitialized || filterState.isVisible) return;
setOrders([]); fetchOrders().then(setOrders)
setIsRefreshing(true); }, [filterState.orderBy,
dispatch(refreshPagination()); filterState.desc,
}, [filterState.orderBy, filterState.desc, filterState.status, filterState.shipmentDate, filterState.isVisible]); filterState.status,
filterState.shipmentDate,
filterState.isVisible,
filterState.shippingWarehouse
]);
useEffect(() => {
fetchOrders().then(newOrders=>{
setOrders(newOrders);
setIsInitialized(true);
})
}, []);
return {refresh, isRefreshing, orders}; return {refresh, isRefreshing, orders};
} }

View File

@@ -16,6 +16,7 @@ import {NavigationProp, useNavigation} from "@react-navigation/native";
import {TabNavigatorParamList} from "../MainScreen/MainScreen"; import {TabNavigatorParamList} from "../MainScreen/MainScreen";
import useOrders from "../OrderScreen/useOrders"; import useOrders from "../OrderScreen/useOrders";
import {nextPage, openOrdersFilterModal} from "../../features/ordersFilter/ordersFilterSlice"; import {nextPage, openOrdersFilterModal} from "../../features/ordersFilter/ordersFilterSlice";
import DTitle from "../../components/DTitle/DTitle";
function OrdersScreen() { function OrdersScreen() {
const dispatch = useDispatch(); const dispatch = useDispatch();
@@ -52,6 +53,10 @@ function OrdersScreen() {
ItemSeparatorComponent={flashListSeparator} ItemSeparatorComponent={flashListSeparator}
/> />
{orders.length <= 0 &&
<DTitle style={{flex: 1, textAlign: "center"}}>Нет заказов по заданному фильтру</DTitle>
}
</View> </View>
</View> </View>

View File

@@ -1,18 +1,17 @@
import {Button, Text, View, StyleSheet, TouchableOpacity, Image, ScrollView, GestureResponderEvent} from "react-native"; import {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"; import {responsiveFontSize, responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
import {background, blue, gray} from "../../css/colors"; import {blue, gray} from "../../css/colors";
import {RFPercentage} from "react-native-responsive-fontsize"; import {RFPercentage} from "react-native-responsive-fontsize";
import DText from "../../components/DText/DText"; import DText from "../../components/DText/DText";
import DTitle from "../../components/DTitle/DTitle"; import DTitle from "../../components/DTitle/DTitle";
import {ScreenStackHeaderLeftView} from "react-native-screens";
import Separator from "../../components/Separator/Separator"; import Separator from "../../components/Separator/Separator";
import {BottomSheetModal} from "@gorhom/bottom-sheet"; import {BottomSheetModal} from "@gorhom/bottom-sheet";
import {useMemo, useRef, useState} from "react"; import {useMemo, useRef, useState} from "react";
import {openApp} from "rn-openapp"; import assemblyApi from "../../api/assemblyApi";
import SelectProductModal from "../../components/Modals/SelectProductModal/SelectProductModal"; import Toast from "react-native-toast-message";
import selectProductModal from "../../components/Modals/SelectProductModal/SelectProductModal"; import {useDispatch} from "react-redux";
import {reset} from "../../features/assembly/assemblySlice";
type SettingsElementProps = { type SettingsElementProps = {
icon: any; icon: any;
@@ -58,6 +57,7 @@ function ProfileScreen() {
const bottomSheetModalRef = useRef<BottomSheetModal>(null); const bottomSheetModalRef = useRef<BottomSheetModal>(null);
const snapPoints = useMemo(() => ['25%', '40%'], []); const snapPoints = useMemo(() => ['25%', '40%'], []);
const [modalVisible, setModalVisible] = useState(false); const [modalVisible, setModalVisible] = useState(false);
const dispatch = useDispatch();
return ( return (
<View style={styles.container}> <View style={styles.container}>
<View style={styles.header}> <View style={styles.header}>
@@ -76,9 +76,18 @@ function ProfileScreen() {
> >
<SettingsElement icon={require('assets/icons/settings/withdraw.png')} title={'Вывод'}/> <SettingsElement icon={require('assets/icons/settings/withdraw.png')} title={'Вывод'}/>
<SettingsElement icon={require('assets/icons/settings/statistics.png')} title={'Статистика'}/> <SettingsElement icon={require('assets/icons/settings/statistics.png')} title={'Статистика'}/>
<SettingsElement icon={require('assets/icons/settings/printer.png')} title={'Принтеры'}/>
<SettingsElement onPress={() => { <SettingsElement onPress={() => {
openApp('assemblrprintingservice'); assemblyApi.cancel().then(response => {
}} icon={require('assets/icons/settings/printer.png')} title={'Принтеры'}/>
Toast.show({
type: response.ok ? "success" : "error",
text1: "Отмена сборки",
text2: response.message,
});
dispatch(reset());
})
}} icon={require('assets/icons/settings/close.png')} title={'Отменить сборку'}/>
</ScrollView> </ScrollView>
</View> </View>
<Separator/> <Separator/>
@@ -187,7 +196,7 @@ const styles = StyleSheet.create({
backgroundColor: "white", backgroundColor: "white",
borderRadius: 100, borderRadius: 100,
borderWidth: RFPercentage(0.3), borderWidth: RFPercentage(0.3),
padding: RFPercentage(1), padding: RFPercentage(1.5),
justifyContent: "center", justifyContent: "center",
alignItems: "center" alignItems: "center"

6
src/types/product.ts Normal file
View File

@@ -0,0 +1,6 @@
export type Product = {
productId: number;
dencoArticle: number;
productName: string;
thumbUrl: string;
}

View File

@@ -0,0 +1,4 @@
export type ShippingWarehouse = {
id: number;
name: string;
}

View File

@@ -1,4 +1,5 @@
import axios from "axios"; import axios from "axios";
import {blue100} from "react-native-paper/lib/typescript/styles/themes/v2/colors";
class PrintingService { class PrintingService {
private static instance: PrintingService | null = null; private static instance: PrintingService | null = null;
@@ -17,9 +18,24 @@ class PrintingService {
this.port = port; this.port = port;
} }
private newAbortSignal(timeoutMs: number) {
const abortController = new AbortController();
setTimeout(() => abortController.abort(), timeoutMs || 0);
return abortController.signal;
}
private async print(printer: string, type: string, bytes: Uint8Array): Promise<boolean> { private async print(printer: string, type: string, bytes: Uint8Array): Promise<boolean> {
let response = await axios.post(`http://${this.apiUrl}:${this.port}/print/${printer}/${type}`, bytes.buffer); try {
let response = await axios.post(`http://${this.apiUrl}:${this.port}/print/${printer}/${type}`, bytes.buffer, {
timeout: 20 * 1000,
signal: this.newAbortSignal(20 * 1000)
});
return response.data.ok; return response.data.ok;
} catch (error) {
console.log(error);
return false;
}
} }
public async printPdf(printer: string, pdfBytes: Uint8Array): Promise<boolean> { public async printPdf(printer: string, pdfBytes: Uint8Array): Promise<boolean> {
@@ -29,6 +45,11 @@ class PrintingService {
public async printImage(printer: string, imageBytes: Uint8Array): Promise<boolean> { public async printImage(printer: string, imageBytes: Uint8Array): Promise<boolean> {
return this.print(printer, "image", imageBytes); return this.print(printer, "image", imageBytes);
} }
public getPrinter(baseMarketplace: number): string {
if (baseMarketplace == 0) return "wildberries";
return "ozon";
}
} }
export default PrintingService; export default PrintingService;