diff --git a/src/components/BasicButton/BasicButton.tsx b/src/components/BasicButton/BasicButton.tsx index ed12442..da5c335 100644 --- a/src/components/BasicButton/BasicButton.tsx +++ b/src/components/BasicButton/BasicButton.tsx @@ -3,6 +3,7 @@ import {StyleSheet, TouchableOpacity, Text, View, StyleProp, ViewStyle, GestureR import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions"; import {RFPercentage} from "react-native-responsive-fontsize"; import DText from "../DText/DText"; +import DTitle from "../DTitle/DTitle"; type Props = { label: string; @@ -15,10 +16,11 @@ type Props = { const BasicButton: FC = ({label, onPress, containerStyle, style, isUnset = false, disabled = false}) => { return ( - - - - {label} + + + + {label} + @@ -33,15 +35,15 @@ const styles = StyleSheet.create({ alignContent: "center", backgroundColor: '#2478F8', borderRadius: responsiveWidth(1), - padding: responsiveWidth(2), - flex: 1 + padding: responsiveWidth(3), }, text: { color: "white", fontSize: RFPercentage(2), textAlignVertical: "center", - textAlign: "center" - } + textAlign: "center", + }, + textWrapper: {} }); export default BasicButton; \ No newline at end of file diff --git a/src/components/Modals/AcceptModal/AcceptModal.tsx b/src/components/Modals/AcceptModal/AcceptModal.tsx new file mode 100644 index 0000000..05342eb --- /dev/null +++ b/src/components/Modals/AcceptModal/AcceptModal.tsx @@ -0,0 +1,65 @@ +import {FC} from "react"; +import {StyleSheet, View} from "react-native"; +import {BottomSheetModalProvider} from "@gorhom/bottom-sheet"; +import {useSelector} from "react-redux"; +import {RootState} from "../../../redux/store"; +import Modal from "react-native-modal"; +import {background, blue} from "../../../css/colors"; +import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions"; +import * as Progress from 'react-native-progress'; +import DTitle from "../../DTitle/DTitle"; +import {RFPercentage} from "react-native-responsive-fontsize"; +import BasicButton from "../../BasicButton/BasicButton"; + +type Props = { + visible: boolean; + text: string; + onAccepted: () => void; + onRefused: () => void; +} +const AcceptModal: FC = ({visible, text, onAccepted, onRefused}) => { + + return ( + + + {text} + + onAccepted()}/> + onRefused()}/> + + + + + ) +} + +const styles = StyleSheet.create({ + container: { + alignSelf: "center", + backgroundColor: background, + width: responsiveWidth(80), + height: responsiveHeight(50), + borderRadius: responsiveWidth(2), + paddingHorizontal: responsiveWidth(15), + paddingVertical: responsiveHeight(10), + justifyContent: "center", + rowGap: responsiveHeight(5) + }, + buttonsWrapper: { + flexDirection: "row", + justifyContent: "space-between", + }, + progressBar: { + justifyContent: "center" + }, + button: { + paddingHorizontal: responsiveWidth(8) + }, + acceptButton: {}, + refuseButton: { + backgroundColor: "red" + }, +}) +export default AcceptModal; \ No newline at end of file diff --git a/src/components/Modals/ReprintModal/ReprintModal.tsx b/src/components/Modals/ReprintModal/ReprintModal.tsx new file mode 100644 index 0000000..de58c6e --- /dev/null +++ b/src/components/Modals/ReprintModal/ReprintModal.tsx @@ -0,0 +1,37 @@ +import {FC, useState} from "react"; +import AcceptModal from "../AcceptModal/AcceptModal"; +import PrintingService from "../../../utils/PrintingService"; +import {useDispatch, useSelector} from "react-redux"; +import {RootState} from "../../../redux/store"; +import {closeReprintModal, openReprintModal} from "../../../features/reprintModal/reprintModalSlice"; +import PrintingApi from "../../../api/printingApi"; +import {closeLoadingModal, openLoadingModal} from "../../../features/loadingModal/loadingModalSlice"; + + +const ReprintModal: FC = () => { + const printerName = useSelector((state: RootState) => state.printing.printerName); + const order = useSelector((state: RootState) => state.assembly.order); + const isVisible = useSelector((state: RootState) => state.reprintModal.isVisible); + const dispatch = useDispatch(); + return ( { + if (!printerName || !order) return; + dispatch(closeReprintModal()); + dispatch(openLoadingModal()); + PrintingApi.getLabel(order.databaseId).then(pdfBytes => { + PrintingService.getInstance().printPdf(printerName, pdfBytes).then(r => { + dispatch(closeLoadingModal()); + if (r) dispatch(closeReprintModal()); + else dispatch(openReprintModal()); + }); + }) + }} + onRefused={() => { + dispatch(closeReprintModal()) + dispatch(closeLoadingModal()); + }}/>) +} + +export default ReprintModal; \ No newline at end of file diff --git a/src/components/OrderCard/OrderCard.tsx b/src/components/OrderCard/OrderCard.tsx index 561cb91..f009ff7 100644 --- a/src/components/OrderCard/OrderCard.tsx +++ b/src/components/OrderCard/OrderCard.tsx @@ -6,18 +6,20 @@ import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensi import DTitle from "../DTitle/DTitle"; import {Order} from "../../types/order"; import OrderProductsList from "./OrderProductsList"; +import {useDispatch} from "react-redux"; +import {setOrder} from "../../features/assembly/assemblySlice"; type Props = { onPress?: (event: GestureResponderEvent) => void - onSelect: (order: Order) => void order: Order + onSelect: (order: Order) => void } -const OrderCard: FC = React.memo(({onPress, onSelect, order}) => { - +const OrderCard: FC = ({onPress, onSelect, order}) => { return ( - { - onSelect(order); + { + if (onSelect) onSelect(order); + }}> @@ -28,6 +30,7 @@ const OrderCard: FC = React.memo(({onPress, onSelect, order}) => { Селлер: {order.sellerName} Маркетплейс: {order.marketplaceName} + Товаров в заказе: {order.products.length} @@ -40,13 +43,12 @@ const OrderCard: FC = React.memo(({onPress, onSelect, order}) => { ) -}) +} const styles = StyleSheet.create({ container: { backgroundColor: "white", display: "flex", borderRadius: RFPercentage(3), - height: responsiveHeight(15), flexDirection: "row", padding: RFPercentage(2), flex: 1 @@ -80,7 +82,6 @@ const styles = StyleSheet.create({ marginLeft: responsiveHeight(1) }, descriptionContent: { - // backgroundColor: "green", flex: 1 }, descriptionStatus: { diff --git a/src/components/SearchBar/ScanModal.tsx b/src/components/SearchBar/ScanModal.tsx index 9cba548..63f8fba 100644 --- a/src/components/SearchBar/ScanModal.tsx +++ b/src/components/SearchBar/ScanModal.tsx @@ -14,9 +14,12 @@ const ScanModal: FC = () => { const visible = useSelector((state: RootState) => state.scanModal.isVisible); const dispatch = useDispatch(); useEffect(() => { - if (visible) inputRef.current?.focus(); + // if (visible) inputRef.current?.focus(); + if (visible){ + dispatch(setScannedData('4750735280715')); + dispatch(closeScanModal()) + } }, [visible]); - return ( diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx index 6716676..c0cc417 100644 --- a/src/components/SearchBar/SearchBar.tsx +++ b/src/components/SearchBar/SearchBar.tsx @@ -25,10 +25,12 @@ const SearchBar: FC = ({onSearch, onSupplierProductSelected}) => { const scannedData = useSelector((state: RootState) => state.scanModal.scannedData); useEffect(() => { if (!scannedData) return; - barcodeApi.searchProducts(scannedData).then(setProducts); + barcodeApi.searchProducts(scannedData).then((response) => { + console.log("Response: " + response); + setProducts(response) + }); }, [scannedData]); - - const selectProductModalVisible = products.length > 0 && false; + const selectProductModalVisible = products.length > 0; return ( @@ -74,6 +76,7 @@ const styles = StyleSheet.create({ borderRadius: 0, borderTopRightRadius: responsiveWidth(1), borderBottomRightRadius: responsiveWidth(1), + paddingHorizontal: responsiveWidth(5) }, scanImageWrapper: { paddingHorizontal: responsiveWidth(1), @@ -85,14 +88,13 @@ const styles = StyleSheet.create({ }, textInput: { height: responsiveHeight(height), - flex: 2, + flex: 1, borderWidth: 1, borderColor: "#A5A5A5", borderTopLeftRadius: responsiveWidth(1), borderBottomLeftRadius: responsiveWidth(1), paddingLeft: responsiveHeight(2), fontSize: RFPercentage(2), - // fontFamily: 'SF Pro Text' } }) diff --git a/src/features/assembly/assemblySlice.ts b/src/features/assembly/assemblySlice.ts index 93ed35b..6d96e96 100644 --- a/src/features/assembly/assemblySlice.ts +++ b/src/features/assembly/assemblySlice.ts @@ -1,4 +1,4 @@ -import {createSlice} from "@reduxjs/toolkit"; +import {createSlice, PayloadAction} from "@reduxjs/toolkit"; import {Order} from "../../types/order"; export interface AssemblyState { @@ -16,13 +16,30 @@ export const assembly = createSlice({ setOrder: (state, action) => { state.order = action.payload; }, - setAssembled: (state, payload) => { - state.order = ({...state.order, orderNumber:"2282"}) + setAssembled: (state, action: PayloadAction<{ orderProductId: number }>) => { + if (!state.order || !state.order.products) return; + + const orderProductId = action.payload.orderProductId; + + state.order.products.forEach(product => { + if (product.databaseId === orderProductId) { + product.assembled = true; + } + }); }, - setShipped: (state) => { + setShipped: (state, action: PayloadAction<{ orderProductId: number }>) => { + if (!state.order || !state.order.products) return; + + const orderProductId = action.payload.orderProductId; + + state.order.products.forEach(product => { + if (product.databaseId === orderProductId) { + product.shipped = true; + } + }); } } }) -export const {setOrder} = assembly.actions; +export const {setOrder, setAssembled, setShipped} = assembly.actions; export default assembly.reducer; \ No newline at end of file diff --git a/src/features/printing/printingSlice.ts b/src/features/printing/printingSlice.ts new file mode 100644 index 0000000..5b3ee7b --- /dev/null +++ b/src/features/printing/printingSlice.ts @@ -0,0 +1,22 @@ +import {createSlice, PayloadAction} from "@reduxjs/toolkit"; + +export interface PrintingState { + printerName?: string; +} + +const initialState: PrintingState = { + printerName: undefined, +} +export const printingSlice = createSlice({ + name: 'printing', + initialState, + reducers: { + setPrinterName: (state, action: PayloadAction<{ printerName: string }>) => { + state.printerName = action.payload.printerName; + } + + } +}); + +export const {setPrinterName} = printingSlice.actions; +export default printingSlice.reducer; \ No newline at end of file diff --git a/src/features/reprintModal/reprintModalSlice.ts b/src/features/reprintModal/reprintModalSlice.ts new file mode 100644 index 0000000..b2aabc8 --- /dev/null +++ b/src/features/reprintModal/reprintModalSlice.ts @@ -0,0 +1,25 @@ +import {createSlice} from "@reduxjs/toolkit"; + +export interface ReprintModalState { + isVisible: boolean; +} + +const initialState: ReprintModalState = { + isVisible: false, +} + +export const reprintModalSlice = createSlice({ + name: 'reprintModal', + initialState, + reducers: { + openReprintModal: (state) => { + state.isVisible = true + }, + closeReprintModal: (state) => { + state.isVisible = false + }, + } +}) + +export const {openReprintModal, closeReprintModal} = reprintModalSlice.actions; +export default reprintModalSlice.reducer; \ No newline at end of file diff --git a/src/redux/store.ts b/src/redux/store.ts index d2a75d1..c5593c9 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -5,6 +5,8 @@ import interfaceReducer from 'features/interface/interfaceSlice'; import scanModalReducer from 'features/scanModal/scanModalSlice'; import loadingModalReducer from 'features/loadingModal/loadingModalSlice'; import assemblyReducer from 'features/assembly/assemblySlice'; +import printingReducer from 'features/printing/printingSlice'; +import reprintModalReducer from 'features/reprintModal/reprintModalSlice'; import {useDispatch} from "react-redux"; export const store = configureStore({ @@ -13,7 +15,9 @@ export const store = configureStore({ interface: interfaceReducer, scanModal: scanModalReducer, loadingModal: loadingModalReducer, - assembly: assemblyReducer + assembly: assemblyReducer, + printing: printingReducer, + reprintModal: reprintModalReducer }, }); diff --git a/src/screens/CommonPage/CommonPage.tsx b/src/screens/CommonPage/CommonPage.tsx index e4f3164..52d1faf 100644 --- a/src/screens/CommonPage/CommonPage.tsx +++ b/src/screens/CommonPage/CommonPage.tsx @@ -13,6 +13,7 @@ import toastConfig from "../../components/Toast/Toast"; import ScanModal from "../../components/SearchBar/ScanModal"; import {closeScanModal, setScannedData} from "../../features/scanModal/scanModalSlice"; import LoadingModal from "../../components/Modals/LoadingModal/LoadingModal"; +import ReprintModal from "../../components/Modals/ReprintModal/ReprintModal"; function CommonPage() { const dim = useSelector((state: RootState) => state.interface.dim); @@ -37,6 +38,7 @@ function CommonPage() { + diff --git a/src/screens/MainScreen/MainScreen.tsx b/src/screens/MainScreen/MainScreen.tsx index 5a162a8..1578816 100644 --- a/src/screens/MainScreen/MainScreen.tsx +++ b/src/screens/MainScreen/MainScreen.tsx @@ -6,7 +6,7 @@ import moneyScreen from "../MoneyScreen/MoneyScreen"; import profileScreen from "../ProfileScreen/ProfileScreen"; import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions"; import {RFPercentage} from "react-native-responsive-fontsize"; -import OrderScreen from "../OrderScreen/OrderScreen"; +import OrderScreen, {OrderScreenController} from "../OrderScreen/OrderScreen"; import OrdersScreen from "../OrdersScreen/OrdersScreen"; import {background} from "../../css/colors"; import * as fs from "fs"; @@ -50,7 +50,7 @@ function MainScreen() { const tabScreens: CustomTabProps[] = [ { name: "Home", - component: OrderScreen, + component: OrderScreenController, icon: require('assets/icons/home.png'), hidden: false }, diff --git a/src/screens/OrderScreen/OrderScreen.tsx b/src/screens/OrderScreen/OrderScreen.tsx index bf3f7d7..77c5f04 100644 --- a/src/screens/OrderScreen/OrderScreen.tsx +++ b/src/screens/OrderScreen/OrderScreen.tsx @@ -5,7 +5,7 @@ import {RFPercentage} from "react-native-responsive-fontsize"; import DTitle from "../../components/DTitle/DTitle"; import BasicButton from "../../components/BasicButton/BasicButton"; import Hyperlink from "../../components/Hyperlink/Hyperlink"; -import React, {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'; @@ -16,17 +16,25 @@ import {useNavigation} from "@react-navigation/native"; import printingApi from "../../api/printingApi"; import PrintingService from "../../utils/PrintingService"; import OrderProductsList from "../../components/OrderCard/OrderProductsList"; -import {setOrder} from "../../features/assembly/assemblySlice"; +import {setAssembled, setOrder} from "../../features/assembly/assemblySlice"; +import AcceptModal from "../../components/Modals/AcceptModal/AcceptModal"; +import {Order} from "../../types/order"; +import acceptModal from "../../components/Modals/AcceptModal/AcceptModal"; +import printingService from "../../utils/PrintingService"; +import ReprintModal from "../../components/Modals/ReprintModal/ReprintModal"; +import {setData, setPrinterName} from "../../features/printing/printingSlice"; +import {openReprintModal} from "../../features/reprintModal/reprintModalSlice"; +type AssemblyPeriod = { + started: Date; + ended: Date; +} -function OrderScreen() { - - const order = useSelector((state: RootState) => state.assembly.order); - if (!order) return ( +const NoOrderScreen: FC = () => { + return ( Заказ не выбран! - { // @ts-ignore navigator.navigate("Box"); @@ -35,16 +43,68 @@ function OrderScreen() { ) - const dispatch = useDispatch(); - const assembled = useSelector((state: RootState) => state.assembly.assembled); - const shipped = useSelector((state: RootState) => state.assembly.shipped); +} +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 = ({order}) => { const navigator = useNavigation(); + const dispatch = useDispatch(); const [selectedProduct, setSelectedProduct] = useState(order.products[0]); + const [assemblyPeriod, setAssemblyPeriod] = useState({started: new Date(), ended: new Date()}) + const [acceptModalVisible, setAcceptModalVisible] = useState(false); + const [reprintModalVisible, setReprintModalVisible] = useState(false); + + const [assemblyStarted, setAssemblyStarted] = useState(false); + const [assemblyIntervalId, setAssemblyIntervalId] = useState(0); + const prettyPrintMilliseconds = (milliseconds: number) => { + const totalSeconds = Math.floor(milliseconds / 1000); + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; + + // Форматируем минуты и секунды, чтобы они всегда были двузначными + const formattedMinutes = String(minutes).padStart(2, '0'); + const formattedSeconds = String(seconds).padStart(2, '0'); + + return `${formattedMinutes}:${formattedSeconds}`; + } + const startAssembly = async () => { + setAssemblyStarted(true); + let intervalId = setInterval(() => { + setAssemblyPeriod(oldValue => ({...oldValue, ended: new Date()})) + }, 1000); + setAssemblyIntervalId(Number(intervalId)) + }; + useEffect(() => { + let newSelectedProduct = order.products.find(o => o.databaseId == selectedProduct.databaseId); + if (!newSelectedProduct) return; + setSelectedProduct(newSelectedProduct); + }, [order]); + useEffect(() => { + dispatch(closeLoadingModal()) + }, []); return ( - { + { + if (!order) return; + setSelectedProduct(product) }}/> @@ -62,37 +122,68 @@ function OrderScreen() { Артикул DENCO: {selectedProduct.dencoArticle} Поставщик: {selectedProduct.supplierName} Номер товара: {0} + {} + Сборка + Затрачено + времени: {prettyPrintMilliseconds(assemblyPeriod.ended.getTime() - assemblyPeriod.started.getTime())} + - { - setOrder(oldValue => ({ ...oldValue })); - }} - /> - product.assembled))}/> + {assemblyStarted ? <> + { + dispatch(setAssembled({orderProductId: selectedProduct.databaseId})) + }} + /> + { + if (!order) return; + dispatch(setLoadingText('Идет печать этикетки...')) + dispatch(openLoadingModal()) + printingApi.getLabel(order.databaseId).then(pdfData => { + printingService.getInstance().printPdf('ozon', pdfData).then((response) => { + dispatch(closeLoadingModal()); + if (response) return; + dispatch(setPrinterName({printerName: 'ozon'})); + dispatch(openReprintModal()); + }); + }) + }} + containerStyle={styles.buttonContainer} + label={"Печать этикетки"} + disabled={Boolean(order.products.find(product => !product.assembled))}/> + + : setAcceptModalVisible(true)} + label={"Начать сборку"}/>} + - + { + setAcceptModalVisible(false); + startAssembly(); + }} + onRefused={() => { + setAcceptModalVisible(false); + }}/> + ) } -const noOrderStyles = StyleSheet.create({ - container: { - justifyContent: "center", - flex: 1, - rowGap: responsiveHeight(5) - }, - title: { - textAlign: "center", - fontSize: responsiveWidth(5) - }, - buttonWrapper: { - paddingHorizontal: responsiveWidth(20) - } -}) +export const OrderScreenController: FC = () => { + const order: Order | undefined = useSelector((state: RootState) => state.assembly.order); + if (!order) return + console.log(order) + return +} + + const styles = StyleSheet.create({ orderProductsListWrapper: { flex: 0.5, @@ -100,9 +191,8 @@ const styles = StyleSheet.create({ borderRadius: RFPercentage(3), padding: RFPercentage(2), }, - buttonWrapper: { + buttonContainer: { flex: 1, - backgroundColor: "red" }, dataContainer: { backgroundColor: "white", diff --git a/src/screens/OrdersScreen/OrdersScreen.tsx b/src/screens/OrdersScreen/OrdersScreen.tsx index f403697..b03fd31 100644 --- a/src/screens/OrdersScreen/OrdersScreen.tsx +++ b/src/screens/OrdersScreen/OrdersScreen.tsx @@ -1,27 +1,18 @@ -import {ActivityIndicator, FlatList, ScrollView, StyleSheet, View} from "react-native"; +import {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, useSelector} from "react-redux"; -import {enableDim, disableDim} from "../../features/interface/interfaceSlice"; -import {BottomSheetModal} from "@gorhom/bottom-sheet"; -import {useEffect, useMemo, useRef, useState} from "react"; -import {RadioButton, Button} from "react-native-paper"; -import DText from "../../components/DText/DText"; -import {background, blue} from "../../css/colors"; -import DTitle from "../../components/DTitle/DTitle"; -import BasicButton from "../../components/BasicButton/BasicButton"; -import {generateRandomOrders, Order} from "../../types/order"; +import {useDispatch} from "react-redux"; +import {useEffect, useRef, useState} from "react"; +import {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"; import {setOrder} from "../../features/assembly/assemblySlice"; import {NavigationProp, useNavigation} from "@react-navigation/native"; @@ -61,10 +52,7 @@ function OrdersScreen() { data={orders} keyExtractor={(item: Order) => item.orderNumber.toString()} renderItem={({item}) => - { - dispatch(setOrder(order)); - navigator.navigate("Home"); - }}/>} + dispatch(setOrder(order))} order={item}/>} showsHorizontalScrollIndicator={false} showsVerticalScrollIndicator={true} onEndReachedThreshold={0.1} diff --git a/src/utils/PrintingService.ts b/src/utils/PrintingService.ts index 0e520c9..27254f4 100644 --- a/src/utils/PrintingService.ts +++ b/src/utils/PrintingService.ts @@ -17,16 +17,16 @@ class PrintingService { this.port = port; } - private async print(printer: string, type: string, bytes: Uint8Array): Promise { + private async print(printer: string, type: string, bytes: Uint8Array): Promise { let response = await axios.post(`http://${this.apiUrl}:${this.port}/print/${printer}/${type}`, bytes.buffer); - return response.data; + return response.data.ok; } - public async printPdf(printer: string, pdfBytes: Uint8Array): Promise { + public async printPdf(printer: string, pdfBytes: Uint8Array): Promise { return this.print(printer, "pdf", pdfBytes); } - public async printImage(printer: string, imageBytes: Uint8Array): Promise { + public async printImage(printer: string, imageBytes: Uint8Array): Promise { return this.print(printer, "image", imageBytes); } }