This commit is contained in:
2024-10-12 03:55:19 +03:00
parent c0da607b72
commit 55ba06295e
37 changed files with 1200 additions and 156 deletions

View File

@@ -87,7 +87,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.1.7"
versionName "1.3.4"
buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString())
}
@@ -168,6 +168,7 @@ dependencies {
exclude group:'com.squareup.okhttp3', module:'okhttp'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}")
implementation(files("libs/printer-lib-2.2.4.aar"))
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")

View File

@@ -1,6 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.VIBRATE"/>
@@ -27,6 +29,7 @@
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="com.anonymous.Assemblr"/>
<data android:scheme="exp+assemblr"/>
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false"/>

View File

@@ -0,0 +1,41 @@
package com.anonymous.Assemblr;
import android.util.Log;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import net.posprinter.IDeviceConnection;
import net.posprinter.POSConnect;
import net.posprinter.TSCPrinter;
import java.util.HashMap;
import java.util.Map;
public class AwesomeModule extends ReactContextBaseJavaModule {
private final ReactApplicationContext context;
private final Map<String, IDeviceConnection> printersMap = new HashMap<>();
AwesomeModule(ReactApplicationContext context) {
super(context);
this.context = context;
POSConnect.init(this.context);
}
public void initPrinters(){
}
@NonNull
@Override
public String getName() {
return "AwesomeModule";
}
@ReactMethod
public void test() {
Log.d("AwesomeModule", "AwesomeModule test");
}
}

View File

@@ -1,4 +1,8 @@
package com.anonymous.Assemblr;
// @generated begin react-native-keyevent-import - expo prebuild (DO NOT MODIFY) sync-6d2345dd84c398e4e99d7d44eb792294628c7e34
import android.view.KeyEvent;
import com.github.kevinejohn.keyevent.KeyEventModule;
// @generated end react-native-keyevent-import
import android.os.Build;
import android.os.Bundle;
@@ -11,6 +15,49 @@ import com.facebook.react.defaults.DefaultReactActivityDelegate;
import expo.modules.ReactActivityDelegateWrapper;
public class MainActivity extends ReactActivity {
// @generated begin react-native-keyevent-body - expo prebuild (DO NOT MODIFY) sync-4ba28f58cc0b4a775aa231e380b7f40e8f34382a
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// // Uncomment this is key events should only trigger once when key is held down
// if (event.getRepeatCount() == 0) {
// KeyEventModule.getInstance().onKeyDownEvent(keyCode, event);
// }
// // This will trigger the key repeat if the key is held down
// // Comment this out if uncommenting the above
KeyEventModule.getInstance().onKeyDownEvent(keyCode, event);
// // Uncomment this if you want the default keyboard behavior
// return super.onKeyDown(keyCode, event);
// // The default keyboard behaviour wll be overridden
// // This is similar to what e.preventDefault() does in a browser
// // comment this if uncommenting the above
super.onKeyDown(keyCode, event);
return true;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
KeyEventModule.getInstance().onKeyUpEvent(keyCode, event);
// // Uncomment this if you want the default keyboard behavior
// return super.onKeyUp(keyCode, event);
// // The default keyboard behaviour wll be overridden
// // This is similar to what e.preventDefault() does in a browser
// // comment this if uncommenting the above
super.onKeyUp(keyCode, event);
return true;
}
@Override
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
KeyEventModule.getInstance().onKeyMultipleEvent(keyCode, repeatCount, event);
return super.onKeyMultiple(keyCode, repeatCount, event);
}
// @generated end react-native-keyevent-body
@Override
protected void onCreate(Bundle savedInstanceState) {
// Set the theme to AppTheme BEFORE onCreate to support

View File

@@ -2,6 +2,7 @@ package com.anonymous.Assemblr;
import android.app.Application;
import android.content.res.Configuration;
import androidx.annotation.NonNull;
import com.facebook.react.PackageList;
@@ -20,61 +21,62 @@ import java.util.List;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHostWrapper(this, new DefaultReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
private final ReactNativeHost mReactNativeHost =
new ReactNativeHostWrapper(this, new DefaultReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
return packages;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new MyAppPackage());
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
return packages;
}
@Override
protected String getJSMainModuleName() {
return ".expo/.virtual-metro-entry";
}
@Override
protected String getJSMainModuleName() {
return ".expo/.virtual-metro-entry";
}
@Override
protected boolean isNewArchEnabled() {
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
}
@Override
protected boolean isNewArchEnabled() {
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
}
@Override
protected Boolean isHermesEnabled() {
return BuildConfig.IS_HERMES_ENABLED;
}
});
@Override
protected Boolean isHermesEnabled() {
return BuildConfig.IS_HERMES_ENABLED;
}
});
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
if (!BuildConfig.REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS) {
ReactFeatureFlags.unstable_useRuntimeSchedulerAlways = false;
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
DefaultNewArchitectureEntryPoint.load();
}
ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
ApplicationLifecycleDispatcher.onApplicationCreate(this);
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig);
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
if (!BuildConfig.REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS) {
ReactFeatureFlags.unstable_useRuntimeSchedulerAlways = false;
}
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
DefaultNewArchitectureEntryPoint.load();
}
ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
ApplicationLifecycleDispatcher.onApplicationCreate(this);
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig);
}
}

View File

@@ -0,0 +1,30 @@
package com.anonymous.Assemblr;
import androidx.annotation.NonNull;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class MyAppPackage implements ReactPackage {
@NonNull
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new AwesomeModule(reactContext));
return modules;
}
}

View File

@@ -24,10 +24,13 @@
"@shopify/flash-list": "1.4.3",
"axios": "^1.5.0",
"babel-plugin-module-resolver": "^5.0.0",
"eas-cli": "^12.5.1",
"expo": "~49.0.8",
"expo-application": "~5.3.0",
"expo-av": "~13.4.1",
"expo-build-properties": "~0.8.3",
"expo-constants": "~14.4.2",
"expo-crypto": "~12.4.1",
"expo-dev-client": "~2.4.12",
"expo-file-system": "~15.4.4",
"expo-intent-launcher": "~10.7.0",
@@ -59,13 +62,13 @@
"react-native-webview": "13.2.2",
"react-redux": "^8.1.2",
"redux": "^4.2.1",
"rn-openapp": "^2.1.2",
"expo-av": "~13.4.1"
"rn-openapp": "^2.1.2"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@types/react": "~18.2.14",
"typescript": "^5.1.3"
},
"private": true
"private": true,
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View File

@@ -1,7 +1,7 @@
import axios from 'axios';
export const baseUrl = 'https://assemblr.denco.store';
// export const baseUrl = 'http://192.168.1.101:5000';
// export const baseUrl = 'https://assemblr.denco.store';
export const baseUrl = 'http://192.168.1.101:5000';
const apiClient = axios.create({
baseURL: baseUrl
});

View File

@@ -3,15 +3,15 @@ import {Assembly} from "../types/assembly";
import {closeCancelAssemblyModal} from "../features/cancelAssemblyModal/cancelAssemblyModalSlice";
const router = '/assembly';
export type CreateAssemblyResponse = {
ok: boolean,
message: string,
assemblyId: number,
statusCode: AssemblyCreationStatusCode,
userName?: string
}
const assemblyApi = {
create: async (orderId: number): Promise<{
ok: boolean,
message: string,
assemblyId: number,
statusCode: AssemblyCreationStatusCode,
userName?: string
}> => {
create: async (orderId: number): Promise<CreateAssemblyResponse> => {
let response = await apiClient.post(`${router}/create`, {orderId});
return response.data;
},
@@ -42,6 +42,14 @@ const assemblyApi = {
cancelById: async (assemblyId: number): Promise<{ ok: boolean, message: string }> => {
let response = await apiClient.post(`${router}/cancelById`, {assemblyId});
return response.data;
},
attachCrpt: async (orderProductId: number, crpt: string): Promise<{ ok: boolean, message: string }> => {
let response = await apiClient.post(`${router}/attachCrpt`, {orderProductId, crpt});
return response.data;
},
needCrpt: async (orderProductId: number): Promise<{ needCrpt: boolean }> => {
let response = await apiClient.get(`${router}/needCrpt`, {params: {orderProductId}});
return response.data;
}
}

View File

@@ -5,7 +5,7 @@ import {RFPercentage} from "react-native-responsive-fontsize";
import DText from "../DText/DText";
import DTitle from "../DTitle/DTitle";
type Props = {
export type BasicButtonProps = {
label: string;
style?: StyleProp<ViewStyle>;
containerStyle?: StyleProp<ViewStyle>;
@@ -14,7 +14,7 @@ type Props = {
disabled?: boolean
};
const BasicButton: FC<Props> = ({label, onPress, containerStyle, style, isUnset = false, disabled = false}) => {
const BasicButton: FC<BasicButtonProps> = ({label, onPress, containerStyle, style, isUnset = false, disabled = false}) => {
return (
<TouchableOpacity style={containerStyle} disabled={disabled} onPress={onPress}>
<View style={[styles.container, style, disabled ? {backgroundColor: "#A0A0A0"} : {}, containerStyle]}>

View File

@@ -7,13 +7,13 @@ import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensi
import DTitle from "../../DTitle/DTitle";
import BasicButton from "../../BasicButton/BasicButton";
type Props = {
export type AcceptModalProps = {
visible: boolean;
text: string;
onAccepted: () => void;
onRefused: () => void;
}
const AcceptModal: FC<Props> = ({visible, text, onAccepted, onRefused}) => {
const AcceptModal: FC<AcceptModalProps> = ({visible, text, onAccepted, onRefused}) => {
return (
<Modal isVisible={visible}>

View File

@@ -11,15 +11,16 @@ import {closeScanModal, setScannedData} from "../../features/scanModal/scanModal
const ScanModal: FC = () => {
const inputRef = useRef<TextInput | null>(null);
const visible = useSelector((state: RootState) => state.scanModal.isVisible);
const state = useSelector((state: RootState) => state.scanModal);
const getDefaultLabel = () => "Наведите сканер на штрихкод товара или заказа";
const dispatch = useDispatch();
useEffect(() => {
if (visible) inputRef.current?.focus();
}, [visible]);
if (state.isVisible) inputRef.current?.focus();
}, [state.isVisible]);
return (
<Modal isVisible={visible}>
<Modal isVisible={state.isVisible}>
<View style={styles.container}>
<DText style={styles.text}>Наведите сканер на штрихкод товара или заказа</DText>
<DText style={styles.text}>{state.customLabel || getDefaultLabel()}</DText>
<BasicButton onPress={() => {
dispatch(closeScanModal());
}} style={styles.cancelButton} label={"Отмена"}/>
@@ -31,7 +32,7 @@ const ScanModal: FC = () => {
style={styles.pseudoInput}
ref={inputRef}
autoFocus={true}
showSoftInputOnFocus={false}
// showSoftInputOnFocus={false}
/>
</View>
</Modal>
@@ -61,9 +62,9 @@ const styles = StyleSheet.create({
},
pseudoInput: {
backgroundColor: "red",
opacity: 0,
// opacity: 0,
position: "absolute",
zIndex: -1
// zIndex: -1
}
})
export default ScanModal;

View File

@@ -51,7 +51,7 @@ const SearchBar: FC<Props> = ({onSearch, onProductSelected}) => {
}} style={styles.scanButton} label={"Поиск"}/>
<View style={styles.scanImageWrapper}>
<TouchableOpacity onPress={() => {
dispatch(openScanModal());
dispatch(openScanModal({customLabel: undefined, data: undefined}));
}}>
<Image style={styles.scanImage} source={require('assets/icons/scan.png')}/>
</TouchableOpacity>

View File

@@ -10,11 +10,13 @@ export interface AssemblyState {
assembly?: Assembly;
selectedProductId?: number;
localState?: ASSEMBLY_STATE;
selectedProduct?: OrderProduct
selectedProduct?: OrderProduct;
acceptModalVisible: boolean;
}
const initialState: AssemblyState = {
localState: ASSEMBLY_STATE.NOT_STARTED
localState: ASSEMBLY_STATE.NOT_STARTED,
acceptModalVisible: false
}
export const assembly = createSlice({
@@ -31,12 +33,18 @@ export const assembly = createSlice({
state.assembly = action.payload;
state.localState = action.payload.state;
},
skipCrpt: (state) => {
if (!state.assembly) return;
state.assembly.state = ASSEMBLY_STATE.ASSEMBLING_PRODUCTS;
state.localState = ASSEMBLY_STATE.ASSEMBLING_PRODUCTS
},
startAssembly: (state) => {
if (!state.assembly) return;
state.assembly.createdAt = (new Date()).toDateString();
state.assembly.state = ASSEMBLY_STATE.ASSEMBLING_PRODUCTS;
state.localState = ASSEMBLY_STATE.ASSEMBLING_PRODUCTS;
state.assembly.state = ASSEMBLY_STATE.SCANNING_CRPT;
state.localState = ASSEMBLY_STATE.SCANNING_CRPT;
},
endAssembly: (state) => {
if (!state.assembly) return;
@@ -94,6 +102,12 @@ export const assembly = createSlice({
state.localState = ASSEMBLY_STATE.NOT_STARTED
state.selectedProductId = undefined;
state.selectedProduct = undefined;
},
openAcceptModal: (state) => {
state.acceptModalVisible = true;
},
closeAcceptModal: (state) => {
state.acceptModalVisible = false;
}
}
})
@@ -107,6 +121,10 @@ export const {
endAssembly,
confirmAssembly,
setLocalState,
reset
reset,
openAcceptModal,
closeAcceptModal,
skipCrpt
} = assembly.actions;
export default assembly.reducer;

View File

@@ -1,30 +1,44 @@
import {createSlice} from "@reduxjs/toolkit";
import {createSlice, PayloadAction} from "@reduxjs/toolkit";
export interface ScanModalState {
isVisible: boolean;
scannedData?: string
scannedData?: string;
customLabel?: string;
data?: any;
uuid?: string;
}
const initialState: ScanModalState = {
isVisible: false,
scannedData: undefined
}
export const scanModalSlice = createSlice({
name: 'scanModal',
initialState,
reducers: {
openScanModal: (state) => {
openScanModalFormContext(state, action: PayloadAction<{ uuid: string }>) {
state.isVisible = true;
state.uuid = action.payload.uuid;
},
openScanModal: (state, action: PayloadAction<{ customLabel?: string, data?: any }>) => {
state.isVisible = true
if (!action) return;
state.customLabel = action.payload.customLabel;
state.data = action.payload.data;
},
closeScanModal: (state) => {
state.isVisible = false
state.customLabel = undefined;
state.data = undefined;
},
setScannedData: (state, action) => {
state.scannedData = action.payload;
},
resolveUuid: (state) => {
state.uuid = undefined;
}
}
})
export const {openScanModal, closeScanModal, setScannedData} = scanModalSlice.actions;
export const {openScanModal, closeScanModal, setScannedData, openScanModalFormContext,resolveUuid} = scanModalSlice.actions;
export default scanModalSlice.reducer;

24
src/hooks/useScan.tsx Normal file
View File

@@ -0,0 +1,24 @@
import {useDispatch, useSelector} from "react-redux";
import {RootState} from "../redux/store";
import {useEffect, useState} from "react";
import {openScanModal} from "../features/scanModal/scanModalSlice";
type ScanProps<T> = {
label: string;
onScanned: (scanned: string, data: T) => void;
}
type ReturnValue<T> = {
scan: (props: ScanProps<T>) => void;
}
function useScan<T>(): ReturnValue<T> {
const state = useSelector((state: RootState) => state.scanModal);
const dispatch = useDispatch();
const scan = (props: ScanProps<T>) => {
dispatch(openScanModal({customLabel: props.label, data: props}));
}
useEffect(() => {
}, [state.scannedData]);
return {scan}
}

View File

@@ -0,0 +1,69 @@
import {createContext, useContext, useEffect, useState} from "react";
import {randomUUID} from "expo-crypto";
import {RootState, useAppDispatch} from "../redux/store";
import {openScanModalFormContext, resolveUuid} from "../features/scanModal/scanModalSlice";
import {useSelector} from "react-redux";
type ScanCallback = (data: string) => void;
type ScanProps = {
callback: ScanCallback;
data?: any;
}
type ScanningContextState = {
scan: (props: ScanProps) => void;
}
const ScanningContext = createContext<ScanningContextState | undefined>(undefined);
export const useScanningContext = () => {
const context = useContext(ScanningContext);
if (!context) {
throw new Error(
"useScanningContext must be used within a ScanningContextProvider"
);
}
return context;
};
type ScanningContextProviderProps = {
children: React.ReactNode;
}
type CallbacksState = {
uuid: string;
callback: (data: string) => void;
}
type DataState = {
uuid: string;
data: any;
}
export const ScanningContextProvider = (props: ScanningContextProviderProps) => {
const [callbacks, setCallbacks] = useState<CallbacksState[]>([]);
const [data, setData] = useState<DataState[]>([]);
const dispatch = useAppDispatch();
const state = useSelector((state: RootState) => state.scanModal);
const scan = (props: ScanProps) => {
const {callback, data} = props
const uuid = randomUUID();
setCallbacks([...callbacks, {uuid, callback}]);
dispatch(openScanModalFormContext({uuid}));
}
const handleChange = () => {
if (!state.scannedData) return;
const uuid = state.uuid;
if (!uuid) return;
const callback = callbacks.find(item => item.uuid === uuid);
if (!callback) return;
callback.callback(state.scannedData);
dispatch(resolveUuid());
setCallbacks(callbacks.filter(item => item.uuid !== uuid));
}
useEffect(() => {
handleChange();
}, [state.scannedData]);
return (
<ScanningContext.Provider value={{scan}}>
{props.children}
</ScanningContext.Provider>
)
}

View File

@@ -0,0 +1,61 @@
import AcceptModal, {AcceptModalProps} from "../../components/Modals/AcceptModal/AcceptModal";
import assemblyApi, {AssemblyCreationStatusCode, CreateAssemblyResponse} from "../../api/assemblyApi";
import Toast from "react-native-toast-message";
import {openCancelAssemblyModal} from "../../features/cancelAssemblyModal/cancelAssemblyModalSlice";
import React from "react";
import {RootState, useAppDispatch} from "../../redux/store";
import {closeAcceptModal} from "../../features/assembly/assemblySlice";
import {useSelector} from "react-redux";
type RestProps = {
onCreated: (response: CreateAssemblyResponse) => void;
}
type Props = RestProps;
const CreateAssemblyModal = (props: Props) => {
const {onCreated} = props
const {order, acceptModalVisible} = useSelector((state: RootState) => state.assembly);
const dispatch = useAppDispatch();
const showCreateStatusToast = (ok: boolean, message: string) => {
Toast.show({
type: ok ? 'success' : 'error',
text1: 'Создание сборки',
text2: message
});
}
const onError = (response: CreateAssemblyResponse) => {
const {statusCode, assemblyId, userName} = response;
if (statusCode !== AssemblyCreationStatusCode.ASSEMBLY_ALREADY_EXISTS) return;
const message =
`Заказ собирает ${userName}. Отменить и начать сборку на ваш аккаунт?\n\n` +
'Удостоверьтесь, что текущая сборка ошибочна и никто другой её не выполняет.';
dispatch(openCancelAssemblyModal({assemblyId: assemblyId, message: message}));
}
const onAccepted = async () => {
if (!order) return
const response = await assemblyApi.create(order.databaseId);
showCreateStatusToast(response.ok, response.message);
const handler = response.ok ? onCreated : onError;
handler(response);
dispatch(closeAcceptModal());
}
const onRefused = async () => {
dispatch(closeAcceptModal())
}
return (
<AcceptModal
visible={acceptModalVisible}
text={`Вы уверены что хотите начать сборку заказа ${order?.orderNumber}`}
onAccepted={onAccepted}
onRefused={onRefused}/>
)
}
export default CreateAssemblyModal;

View File

@@ -0,0 +1,12 @@
import {useEffect, useState} from "react";
import {useSelector} from "react-redux";
import {RootState} from "../../redux/store";
const useAssemblyController = () => {
const state = useSelector((state: RootState) => state.assembly);
useEffect(() => {
}, [state.localState]);
}

View File

@@ -0,0 +1,287 @@
import {useDispatch, useSelector} from "react-redux";
import {RootState, useAppDispatch} from "../../redux/store";
import {ASSEMBLY_STATE} from "../../types/assembly";
import {StyleSheet, Text, View} from "react-native";
import BasicButton, {BasicButtonProps} from "../../components/BasicButton/BasicButton";
import {BaseButton} from "react-native-gesture-handler";
import {
confirmAssembly, endAssembly,
openAcceptModal,
selectProduct,
setAssembled,
skipCrpt
} from "../../features/assembly/assemblySlice";
import {useScanningContext} from "../../providers/ScanProvider";
import assemblyApi from "../../api/assemblyApi";
import {OkMessageResponse} from "../../types/api";
import Toast from "react-native-toast-message";
import {useEffect, useState} from "react";
import {RFPercentage} from "react-native-responsive-fontsize";
import {responsiveHeight} from "react-native-responsive-dimensions";
import {closeLoadingModal, openLoadingModal, setLoadingText} from "../../features/loadingModal/loadingModalSlice";
import printingService from "../../utils/PrintingService";
import printingApi from "../../api/printingApi";
import {setPrinterName} from "../../features/printing/printingSlice";
import {openReprintModal} from "../../features/reprintModal/reprintModalSlice";
import {showReward} from "../../features/animations/animationsSlice";
import {fetchBalance, refreshTransactions} from "../../features/balance/balanceSlice";
import {NavigationProp, useNavigation} from "@react-navigation/native";
import {TabNavigatorParamList} from "../MainScreen/MainScreen";
const AssemblyButton = (props: BasicButtonProps) => {
return (
<BasicButton
{...props}
containerStyle={{flex: 1}}
/>
)
}
const NotStartedButtons = () => {
const dispatch = useDispatch();
const onStartAssembly = () => {
dispatch(openAcceptModal());
}
return (
<AssemblyButton
label={"Начать сборку"}
onPress={onStartAssembly}
/>
)
}
const ScanningCrptButtons = () => {
const dispatch = useAppDispatch();
const state = useSelector((state: RootState) => state.assembly);
const [isInitialized, setIsInitialized] = useState(false);
const getNeedState = () => {
if (!state.order) return {};
return state.order.products.reduce((acc, product) => {
acc[product.databaseId] = false;
return acc;
}, {} as { [key: number]: boolean })
}
const [crptNeedState, setCrptNeedState] = useState<{ [key: number]: boolean }>(getNeedState());
const {scan} = useScanningContext();
const onAttached = (response: OkMessageResponse) => {
Toast.show({
type: response.ok ? "success" : "error",
text1: "Сканирование честного знака",
text2: response.message,
});
if (!response.ok) return
setCrptNeedState(prevState => {
if (!state.selectedProduct) return prevState;
return {...prevState, [state.selectedProduct.databaseId]: false}
})
}
const onScanned = (data: string) => {
if (!state.selectedProduct) return;
assemblyApi.attachCrpt(state.selectedProduct.databaseId, data).then(onAttached);
}
const onScanClick = () => {
scan({callback: onScanned});
}
const initialize = async () => {
dispatch(setLoadingText('Проверка необходимости сканирования честного знака...'))
dispatch(openLoadingModal());
if (!state.order) return;
const responses = await Promise.all(state.order.products.map(async (product) => {
const response = await assemblyApi.needCrpt(product.databaseId);
return {productId: product.databaseId, need: response.needCrpt};
}));
// update state
setCrptNeedState(responses.reduce((acc, {productId, need}) => {
acc[productId] = need;
return acc;
}, {} as { [key: number]: boolean }));
setIsInitialized(true);
dispatch(closeLoadingModal());
////
////
dispatch(selectProduct(0))
}
useEffect(() => {
if (!isInitialized) return;
if (Object.values(crptNeedState).every(value => !value))
dispatch(skipCrpt())
}, [crptNeedState]);
useEffect(() => {
initialize();
}, []);
return (
<>
<AssemblyButton
disabled={!state.selectedProduct || !crptNeedState[state.selectedProduct.databaseId]}
onPress={onScanClick}
label={"Сканировать честный знак"}
/>
<AssemblyButton
onPress={() => {
dispatch(skipCrpt())
}}
label={"Пропустить"}
/>
</>
)
}
const AssemblingButtons = () => {
const dispatch = useAppDispatch();
const state = useSelector((state: RootState) => state.assembly);
useEffect(() => {
dispatch(selectProduct(0));
}, []);
return (
<AssemblyButton
disabled={state.selectedProduct?.assembled}
onPress={() => {
if (!state.selectedProduct) return;
dispatch(setAssembled({orderProductId: state.selectedProduct.databaseId}));
}}
label={"Отметить как собранный"}
/>
)
}
const AllProductsAssembledButtons = () => {
const dispatch = useAppDispatch();
const state = useSelector((state: RootState) => state.assembly);
const [skipButtonVisible, setSkipButtonVisible] = useState(false);
const onConfirmClick = () => {
if (!state.assembly) return;
dispatch(setLoadingText('Подтверждение сборки...'))
dispatch(openLoadingModal());
assemblyApi.confirm(state.assembly.databaseId).then(({ok, message}) => {
dispatch(closeLoadingModal());
Toast.show({
type: ok ? 'success' : 'error',
text1: 'Подтверждение сборки',
text2: message
})
if (ok) {
dispatch(confirmAssembly());
} else {
setSkipButtonVisible(true);
}
})
}
const onSkipClick = () => {
dispatch(confirmAssembly());
}
return (
<>
<AssemblyButton label={"Подтвердить сборку"} onPress={onConfirmClick}/>
{skipButtonVisible && <AssemblyButton label={"Пропустить"} onPress={onSkipClick}/>}
</>
)
}
const ConfirmedButtons = () => {
const navigator = useNavigation<NavigationProp<TabNavigatorParamList, 'Barcode'>>();
const dispatch = useAppDispatch();
const order = useSelector((state: RootState) => state.assembly.order);
const assembly = useSelector((state: RootState) => state.assembly.assembly);
const onPrintLabel = () => {
return;
if (!order) return;
let printer = printingService.getInstance().getPrinter(order.baseMarketplace);
dispatch(setLoadingText('Идет печать этикетки...'))
dispatch(openLoadingModal())
printingApi.getLabel(order.databaseId).then(pdfData => {
printingService.getInstance().printPdf(printer, pdfData).then((response) => {
dispatch(closeLoadingModal());
if (response) return;
dispatch(setPrinterName({printerName: printer}));
dispatch(openReprintModal());
});
})
}
const onEndAssembly = () => {
if (!assembly) return;
assemblyApi.close(assembly.databaseId).then(({ok, message, reward}) => {
Toast.show({
type: ok ? 'success' : 'error',
text1: 'Завершение сборки',
text2: message
});
if (ok) {
dispatch(showReward({reward}));
dispatch(fetchBalance())
dispatch(refreshTransactions())
}
dispatch(endAssembly());
navigator.navigate('Barcode');
})
}
useEffect(() => {
onPrintLabel();
}, []);
return (<>
<AssemblyButton
onPress={onPrintLabel}
label={"Печать этикетки"}/>
<AssemblyButton
onPress={onEndAssembly}
label={"Завершить сборку"}/>
</>)
}
const EndedButtons = () => {
return (<></>)
}
const AssemblyControls = () => {
const state = useSelector((state: RootState) => state.assembly);
const getButtons = () => {
if (state.localState === undefined) return (<BasicButton
label={"Ошибка"}
disabled={true}
/>)
switch (state.localState) {
case ASSEMBLY_STATE.NOT_STARTED:
return <NotStartedButtons/>
case ASSEMBLY_STATE.SCANNING_CRPT:
return <ScanningCrptButtons/>
case ASSEMBLY_STATE.ASSEMBLING_PRODUCTS:
return <AssemblingButtons/>
case ASSEMBLY_STATE.ALL_PRODUCTS_ASSEMBLED:
return <AllProductsAssembledButtons/>
case ASSEMBLY_STATE.CONFIRMED:
return <ConfirmedButtons/>
case ASSEMBLY_STATE.ENDED:
return <EndedButtons/>
}
}
return (<>{getButtons()}</>)
}
const styles = StyleSheet.create({
buttonsContainer: {
backgroundColor: "white",
padding: RFPercentage(2),
flex: 0.5,
borderRadius: RFPercentage(3),
rowGap: responsiveHeight(2)
},
})
export default AssemblyControls;

View File

@@ -0,0 +1,39 @@
import {Order} from "../../types/order";
import React from "react";
import {RFPercentage} from "react-native-responsive-fontsize";
import OrderProductsList from "../../components/OrderCard/OrderProductsList";
import {StyleSheet, View} from "react-native";
import {useAppDispatch} from "../../redux/store";
import {selectProduct} from "../../features/assembly/assemblySlice";
type Props = {
order: Order;
}
const AssemblyProductSelect = (props: Props) => {
const dispatch = useAppDispatch();
const onSelected = (productId: number) => {
dispatch(selectProduct(productId));
}
return (
<View style={styles.wrapper}>
<OrderProductsList
products={props.order.products}
onSelected={onSelected}
/>
</View>
)
}
const styles = StyleSheet.create({
wrapper: {
flex: 0.5,
backgroundColor: "white",
borderRadius: RFPercentage(3),
padding: RFPercentage(2),
},
})
export default AssemblyProductSelect;

View File

@@ -0,0 +1,14 @@
import {useSelector} from "react-redux";
import {RootState} from "../../redux/store";
import {NoOrderScreen} from "../NoOrderScreen/NoOrderScreen";
import AssemblyView from "./AssemblyView";
const AssemblyScreen = () => {
const order = useSelector((state: RootState) => state.assembly.order);
return (
order ? <AssemblyView order={order}/> : <NoOrderScreen/>
)
}
export default AssemblyScreen;

View File

@@ -0,0 +1,92 @@
import {Order} from "../../types/order";
import {RootState, useAppDispatch} from "../../redux/store";
import {useSelector} from "react-redux";
import {StyleSheet, View} from "react-native";
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
import {RFPercentage} from "react-native-responsive-fontsize";
import AssemblyProductSelect from "./AssemblyProductSelect";
import ProductImageView from "./ProductImageView";
import OrderInfoView from "./OrderInfoView";
import AssemblyControls from "./AssemblyControls";
import AcceptAssemblyModal from "./AcceptAssemblyModal";
import assemblyApi, {CreateAssemblyResponse} from "../../api/assemblyApi";
import {setAssembly, startAssembly} from "../../features/assembly/assemblySlice";
import {ScanCrptContextProvider} from "./contexts/ScanCrptContext";
type Props = {
order: Order;
}
const AssemblyView = (props: Props) => {
const {order} = props;
const dispatch = useAppDispatch();
const state = useSelector((state: RootState) => state.assembly);
const onCreated = (_: CreateAssemblyResponse) => {
assemblyApi.getActive().then(assembly => {
dispatch(setAssembly(assembly));
dispatch(startAssembly());
})
}
return (
<View style={styles.viewContainer}>
<View style={styles.topSection}>
<AssemblyProductSelect order={order}/>
<ProductImageView imageUrl={state.selectedProduct?.imageUrl}/>
</View>
<View style={styles.bottomSection}>
<OrderInfoView
order={order}
selectedProduct={state.selectedProduct}
/>
<View style={styles.buttonsWrapper}>
<AssemblyControls/>
</View>
</View>
<AcceptAssemblyModal
onCreated={response => onCreated(response)}
/>
</View>
)
}
const styles = StyleSheet.create({
viewContainer: {
width: "100%",
height: "100%",
display: "flex",
paddingHorizontal: responsiveWidth(5),
paddingBottom: responsiveHeight(10),
rowGap: responsiveHeight(2),
},
topSection: {
display: "flex",
flexDirection: "row",
columnGap: responsiveWidth(3),
height: responsiveHeight(30),
},
productSelectWrapper: {
flex: 0.5,
backgroundColor: "white",
borderRadius: RFPercentage(3),
padding: RFPercentage(2),
},
bottomSection: {
backgroundColor: "red",
flex: 1,
borderRadius: RFPercentage(3),
flexDirection: "row",
columnGap: responsiveWidth(3),
},
buttonsWrapper: {
backgroundColor: "white",
padding: RFPercentage(2),
flex: 0.5,
borderRadius: RFPercentage(3),
rowGap: responsiveHeight(2)
}
})
export default AssemblyView;

View File

@@ -0,0 +1,55 @@
import {Order, OrderProduct} from "../../types/order";
import {ScrollView, StyleSheet, View} from "react-native";
import DTitle from "../../components/DTitle/DTitle";
import DText from "../../components/DText/DText";
import {OrderStatus, OrderStatusDictionary} from "../../features/ordersFilter/ordersFilterSlice";
import React from "react";
import {RFPercentage} from "react-native-responsive-fontsize";
import {responsiveWidth} from "react-native-responsive-dimensions";
type Props = {
order: Order;
selectedProduct?: OrderProduct
}
const OrderInfoView = (props: Props) => {
const {order, selectedProduct} = props;
return (
<View style={styles.dataContainer}>
<ScrollView>
<DTitle style={styles.contentTitle}>Заказ</DTitle>
<DText>Номер заказа: {order.orderNumber}</DText>
<DText>Маркетплейс: {order.marketplaceName}</DText>
<DText>Селлер: {order.sellerName}</DText>
<DText>Создан: {order.createdOn}</DText>
<DText>Отгрузка: {order.shipmentDate}</DText>
<DText>Статус: {OrderStatusDictionary[order.status as OrderStatus]}</DText>
<DText>Склад отгрузки: {order.shippingWarehouse}</DText>
<DText>Город: {order.city}</DText>
<DText>{""}</DText>
<DTitle style={styles.contentTitle}>Товар</DTitle>
<DText>Арт. DENCO: {selectedProduct?.dencoArticle}</DText>
<DText>Арт. поставщика: {selectedProduct?.supplierArticle}</DText>
<DText>Фасовка: {selectedProduct?.inBlock} шт.</DText>
<DText>Поставщик: {selectedProduct?.supplierName}</DText>
</ScrollView>
</View>
)
}
const styles = StyleSheet.create({
dataContainer: {
backgroundColor: "white",
padding: RFPercentage(2),
flex: 0.5,
borderRadius: RFPercentage(3),
},
contentTitle: {
alignSelf: "center"
},
})
export default OrderInfoView;

View File

@@ -0,0 +1,38 @@
import {Image, StyleSheet, TouchableOpacity, View} from "react-native";
import {openImageZoomModal, setImages} from "../../features/imageZoomModal/loadingModalSlice";
import React from "react";
import {RFPercentage} from "react-native-responsive-fontsize";
import {useAppDispatch} from "../../redux/store";
type Props = {
imageUrl?: string;
}
const ProductImageView = (props: Props) => {
const {imageUrl} = props;
const dispatch = useAppDispatch()
return (
<TouchableOpacity style={styles.imageWrapper} onPress={() => {
if (!imageUrl) return;
dispatch(setImages([imageUrl]));
dispatch(openImageZoomModal());
}}>
<View style={{flex: 1}}>
<Image style={styles.image} source={{uri: imageUrl}}/>
</View>
</TouchableOpacity>
)
}
const styles = StyleSheet.create({
imageWrapper: {
flex: 0.5,
backgroundColor: "white",
padding: RFPercentage(2),
borderRadius: RFPercentage(3)
},
image: {
flex: 1,
resizeMode: "contain"
},
});
export default ProductImageView

View File

@@ -0,0 +1,3 @@
const ScanCrptModal = () => {
return (<ScanBa)
}

View File

@@ -0,0 +1,42 @@
// based on ScanCrptContext.tsx (src/screens/AssemblyScreen/contexts/ScanCrptContext.tsx) create new context
import {createContext, FC} from "react";
import {OrderProduct} from "../../../types/order";
import {useDispatch} from "react-redux";
type ScanCrptProps = {
orderProduct: OrderProduct;
onScanned: (data: string) => void;
}
type AssemblyContextState = {
scanCrpt: (props: ScanCrptProps) => void;
}
const AssemblyContext = createContext<AssemblyContextState | undefined>(
undefined
);
const useAssemblyContextState = () => {
const dispatch = useDispatch();
const scanCrpt = (props: ScanCrptProps) => {
}
return {scanCrpt}
};
type AssemblyContextProviderProps = {
children: React.ReactNode;
};
export const AssemblyContextProvider: FC<AssemblyContextProviderProps> = ({
children,
}) => {
const state = useAssemblyContextState();
return (
<AssemblyContext.Provider value={state}>
{children}
</AssemblyContext.Provider>
);
};

View File

@@ -0,0 +1,39 @@
import {OrderProduct} from "../../../types/order";
import {createContext, useContext, useState} from "react";
type ScanCrptContextState = {
currentOrderProduct?: OrderProduct;
}
const ScanCrptContext = createContext<ScanCrptContextState>({});
const useScanCrptContextState = () => {
const [currentOrderProduct, setCurrentOrderProduct] = useState<OrderProduct | undefined>(undefined);
return {
currentOrderProduct,
setCurrentOrderProduct
}
}
type ScanCrptContextProviderProps = {
children: React.ReactNode;
}
export const ScanCrptContextProvider = ({children}: ScanCrptContextProviderProps) => {
const state = useScanCrptContextState();
return (
<ScanCrptContext.Provider value={state}>
{children}
</ScanCrptContext.Provider>
)
}
export const useScanCrptContext = () => {
const context = useContext(ScanCrptContext);
if (!context) {
throw new Error(
"useScanCrptContext must be used within a ScanCrptContextProvider"
);
}
return context;
}

View File

@@ -0,0 +1,14 @@
// Честный знак
import {useEffect, useState} from "react";
import {useSelector} from "react-redux";
import {RootState} from "../../redux/store";
import {ASSEMBLY_STATE} from "../../types/assembly";
import assemblyApi from "../../api/assemblyApi";
const useCrptState = () => {
const assemblyState = useSelector((state: RootState) => state.assembly);
return {
}
}

View File

@@ -34,7 +34,7 @@ function BarcodeScreen() {
if (!navigationState.history) return;
// @ts-ignore
let currentTabKey: string = navigationState.history[navigationState.history.length - 1].key;
if (currentTabKey.startsWith("Barcode")) if (!isScanModalVisible) dispatch(openScanModal());
if (currentTabKey.startsWith("Barcode")) if (!isScanModalVisible) dispatch(openScanModal({}));
}, [navigationState]);
return (
<View style={styles.container}>

View File

@@ -32,6 +32,7 @@ import CancelAssemblyModal from "../../components/Modals/CancelAssemblyModal/Can
import {AxiosHeaders} from "axios";
import apiClient from "../../api/apiClient";
import AnimationsView from "../../components/Animations/AnimationsView";
import {ScanningContextProvider} from "../../providers/ScanProvider";
function CommonPage() {
@@ -65,6 +66,7 @@ function CommonPage() {
}
const checkUpdates = async () => {
return;
const currentVersion = Constants.manifest2?.extra?.expoClient?.version || Constants.manifest?.version;
applicationApi.getVersion('assemblr').then(({latest_version}) => {
@@ -119,20 +121,20 @@ function CommonPage() {
}, []);
return (
<View style={styles.main}>
{isAuthorized ? <MainScreen/> : <LoginScreen/>}
<AnimationsView/>
<View style={[styles.overlay, {display: dim ? 'flex' : 'none'}]}/>
<LoadingModal/>
<ScanModal/>
<ReprintModal/>
<CancelAssemblyModal/>
<ImageZoomModal/>
<SortingModal/>
<Toast config={toastConfig}/>
</View>
<ScanningContextProvider>
<View style={styles.main}>
{isAuthorized ? <MainScreen/> : <LoginScreen/>}
<AnimationsView/>
<View style={[styles.overlay, {display: dim ? 'flex' : 'none'}]}/>
<LoadingModal/>
<ScanModal/>
<ReprintModal/>
<CancelAssemblyModal/>
<ImageZoomModal/>
<SortingModal/>
<Toast config={toastConfig}/>
</View>
</ScanningContextProvider>
)
}

View File

@@ -9,6 +9,7 @@ import {RFPercentage} from "react-native-responsive-fontsize";
import {OrderScreenController} from "../OrderScreen/OrderScreen";
import OrdersScreen from "../OrdersScreen/OrdersScreen";
import {background} from "../../css/colors";
import AssemblyScreen from "../AssemblyScreen/AssemblyScreen";
export type TabNavigatorParamList = {
@@ -50,7 +51,7 @@ function MainScreen() {
const tabScreens: CustomTabProps[] = [
{
name: "Home",
component: OrderScreenController,
component: AssemblyScreen,
icon: require('assets/icons/home.png'),
hidden: false
},

View File

@@ -1,13 +1,24 @@
import {Button, Text, View} from "react-native";
import {useAppDispatch} from "../../redux/store";
import {logoutUser, useGetPokemonByNameQuery} from "../../features/auth/authSlice.ts.back";
import * as process from "process";
import {NativeModules, Text, View} from "react-native";
import BasicButton from "../../components/BasicButton/BasicButton";
import {randomUUID} from "expo-crypto";
import {useScanningContext} from "../../providers/ScanProvider";
import {dampingFor} from "react-native-toast-message/lib/src/components/AnimatedContainer";
const {AwesomeModule} = NativeModules;
function MoneyScreen() {
const {scan} = useScanningContext();
const a = "123";
return (
<View>
<Text style={{fontSize: 36}}>Money</Text>
<BasicButton
onPress={() => {
console.log("Button pressed");
}}
label={"Press me"}
/>
</View>
)
}

View File

@@ -0,0 +1,28 @@
import React, {FC} from "react";
import {StyleSheet, View} from "react-native";
import DText from "../../components/DText/DText";
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
export const NoOrderScreen: FC = () => {
return (
<View style={noOrderStyles.container}>
<DText style={noOrderStyles.title}>Заказ не выбран!</DText>
</View>
)
}
const noOrderStyles = StyleSheet.create({
container: {
justifyContent: "center",
flex: 1,
rowGap: responsiveHeight(5)
},
title: {
textAlign: "center",
fontSize: responsiveWidth(5)
},
buttonWrapper: {
paddingHorizontal: responsiveWidth(20),
}
})

View File

@@ -33,43 +33,19 @@ import {OrderStatus, OrderStatusDictionary} from "../../features/ordersFilter/or
import {openCancelAssemblyModal} from "../../features/cancelAssemblyModal/cancelAssemblyModalSlice";
import {fetchBalance, refreshTransactions} from "../../features/balance/balanceSlice";
import {showReward} from "../../features/animations/animationsSlice";
import {NoOrderScreen} from "../NoOrderScreen/NoOrderScreen";
type AssemblyPeriod = {
started: Date;
ended: Date;
}
const NoOrderScreen: FC = () => {
return (
<View style={noOrderStyles.container}>
<DText style={noOrderStyles.title}>Заказ не выбран!</DText>
</View>
)
}
const noOrderStyles = StyleSheet.create({
container: {
justifyContent: "center",
flex: 1,
rowGap: responsiveHeight(5)
},
title: {
textAlign: "center",
fontSize: responsiveWidth(5)
},
buttonWrapper: {
paddingHorizontal: responsiveWidth(20),
}
})
type OrderScreenProps = {
order: Order;
}
const OrderScreen: FC<OrderScreenProps> = ({order}) => {
const navigator = useNavigation<NavigationProp<TabNavigatorParamList, 'Barcode'>>();
const [orderProductCrpt, setOrderProductCrpt] = useState<Map<number, boolean>>({} as Map<number, boolean>);
const scanState = useSelector((state: RootState) => state.scanModal);
const dispatch = useAppDispatch();
const assembly = useSelector((state: RootState) => state.assembly.assembly);
const assemblyState = useSelector((state: RootState) => state.assembly.localState);
@@ -102,6 +78,25 @@ const OrderScreen: FC<OrderScreenProps> = ({order}) => {
return (<BasicButton containerStyle={styles.buttonContainer}
onPress={() => setAcceptModalVisible(true)}
label={"Начать сборку"}/>)
case ASSEMBLY_STATE.SCANNING_CRPT:
return (
<>
<BasicButton
label={"Сканировать честный знак"}
disabled={selectedProduct ? orderProductCrpt.get(selectedProduct.databaseId) : true}
onPress={() => {
}}
/>
<BasicButton
label={"Пропустить сканирование честного знака"}
onPress={() => {
// dispatch(startAssembly())
}}
style={{backgroundColor: 'orange'}}
/>
</>
)
case ASSEMBLY_STATE.ASSEMBLING_PRODUCTS:
return (<BasicButton
containerStyle={styles.buttonContainer}
@@ -176,6 +171,35 @@ const OrderScreen: FC<OrderScreenProps> = ({order}) => {
)
}
}
const updateAssemblyState = () => {
if (!assembly) return;
assemblyApi.updateState(assembly.databaseId, Number(assemblyState)).then(ok => {
if (ok) return;
Toast.show({
type: 'error',
text1: 'Обновление состояния',
text2: 'Неудалось обновить состояние текущей сборки на сервере'
})
});
}
const onConfirmed = () => {
printLabel();
}
const onAssemblingProducts = () => {
if (!selectedProduct || order.products.length > 1) return;
dispatch(setAssembled({orderProductId: selectedProduct.databaseId}));
}
const onScanCrpt = async () => {
const result = await Promise.all(order.products.map(async product => {
const {need} = await assemblyApi.needCrpt(product.databaseId);
return {databaseId: product.databaseId, need};
}))
setOrderProductCrpt(new Map(result.map(({databaseId, need}) => [databaseId, need])));
}
const printLabel = () => {
if (!order) return;
let printer = printingService.getInstance().getPrinter(order.baseMarketplace);
@@ -190,28 +214,45 @@ const OrderScreen: FC<OrderScreenProps> = ({order}) => {
});
})
}
const handleOrderProductCrptChange = () => {
const someOfProductsNeedCrpt = orderProductCrpt.entries().map(([orderProductId, need]) => {
return need;
}).some(need => need);
if (!someOfProductsNeedCrpt) {
// dispatch(startAssembly());
}
}
const onScanned = () => {
}
useEffect(() => {
if (!scanState.scannedData) return;
onScanned();
}, [scanState.scannedData]);
useEffect(() => {
handleOrderProductCrptChange();
}, [orderProductCrpt]);
useEffect(() => {
if (!assembly) return;
assemblyApi.updateState(assembly.databaseId, Number(assemblyState)).then(ok => {
if (ok) return;
Toast.show({
type: 'error',
text1: 'Обновление состояния',
text2: 'Неудалось обновить состояние текущей сборки на сервере'
})
});
switch (assemblyState) {
case ASSEMBLY_STATE.CONFIRMED:
printLabel();
break;
case ASSEMBLY_STATE.ASSEMBLING_PRODUCTS:
if (!selectedProduct) return;
if (order.products.length == 1) {
dispatch(setAssembled({orderProductId: selectedProduct.databaseId}));
}
break;
if (!assemblyState) return;
updateAssemblyState();
const handlers = {
[ASSEMBLY_STATE.CONFIRMED]: onConfirmed,
[ASSEMBLY_STATE.ASSEMBLING_PRODUCTS]: onAssemblingProducts,
[ASSEMBLY_STATE.SCANNING_CRPT]: onScanCrpt,
[ASSEMBLY_STATE.ALL_PRODUCTS_ASSEMBLED]: () => {
},
[ASSEMBLY_STATE.NOT_STARTED]: () => {
},
[ASSEMBLY_STATE.ENDED]: () => {
}
}
const handler = handlers[assemblyState];
handler();
}, [assemblyState]);
useEffect(() => {
if (!order) return;
@@ -250,7 +291,7 @@ const OrderScreen: FC<OrderScreenProps> = ({order}) => {
<DText>Статус: {OrderStatusDictionary[order.status as OrderStatus]}</DText>
<DText>Склад отгрузки: {order.shippingWarehouse}</DText>
<DText>Город: {order.city}</DText>
<DText>{}</DText>
<DText>{""}</DText>
<DTitle style={styles.contentTitle}>Товар</DTitle>
<DText>Арт. DENCO: {selectedProduct?.dencoArticle}</DText>
<DText>Арт. поставщика: {selectedProduct?.supplierArticle}</DText>
@@ -380,4 +421,3 @@ const styles = StyleSheet.create({
textDecorationLine: 'underline'
}
})
export default OrderScreen;

4
src/types/api.ts Normal file
View File

@@ -0,0 +1,4 @@
export type OkMessageResponse = {
ok: boolean,
message: string
}

View File

@@ -1,5 +1,6 @@
export enum ASSEMBLY_STATE {
NOT_STARTED,
SCANNING_CRPT,
ASSEMBLING_PRODUCTS,
ALL_PRODUCTS_ASSEMBLED,
CONFIRMED,