From aec04846e7b9891da707f54b75ab960d68a8b159 Mon Sep 17 00:00:00 2001 From: olewandowski1 Date: Wed, 17 Dec 2025 15:23:51 +0100 Subject: [PATCH 1/8] OBLS-381 prepare a mocked ui for replenishment --- src/Main.tsx | 427 +++++++++--------- .../PickingShortAndReasonModal/index.tsx} | 0 .../PickingShortAndReasonModal/styles.ts | 55 +++ .../ProductDetails/index.tsx} | 0 src/components/ProductDetails/styles.ts | 40 ++ src/components/Providers.tsx | 16 + src/hooks/useInputFocus.ts | 19 + src/redux/sagas/others.ts | 3 +- src/screens/Dashboard/dashboardData.ts | 7 + .../Picking/PickingPickLocationScreen.tsx | 2 +- .../PickingPickOutboundContainerScreen.tsx | 4 +- .../Picking/PickingPickProductScreen.tsx | 2 +- .../Picking/PickingPickQuantityScreen.tsx | 4 +- .../PickingPickStagingLocationScreen.tsx | 2 +- src/screens/Picking/styles.ts | 113 +---- .../Replenishment/ReplenishmentContext.tsx | 131 ++++++ .../ReplenishmentLocationScreen.tsx | 111 +++++ .../ReplenishmentOutboundContainerScreen.tsx | 181 ++++++++ .../ReplenishmentPickQuantityScreen.tsx | 146 ++++++ .../ReplenishmentProductScreen.tsx | 94 ++++ .../ReplenishmentStagingLocationScreen.tsx | 137 ++++++ src/screens/Replenishment/mock-data.ts | 32 ++ src/screens/Replenishment/styles.ts | 61 +++ src/screens/Replenishment/types.ts | 49 ++ 24 files changed, 1328 insertions(+), 308 deletions(-) rename src/{screens/Picking/PickingShortAndReasonModal.tsx => components/PickingShortAndReasonModal/index.tsx} (100%) create mode 100644 src/components/PickingShortAndReasonModal/styles.ts rename src/{screens/Picking/ProductDetails.tsx => components/ProductDetails/index.tsx} (100%) create mode 100644 src/components/ProductDetails/styles.ts create mode 100644 src/components/Providers.tsx create mode 100644 src/hooks/useInputFocus.ts create mode 100644 src/screens/Replenishment/ReplenishmentContext.tsx create mode 100644 src/screens/Replenishment/ReplenishmentLocationScreen.tsx create mode 100644 src/screens/Replenishment/ReplenishmentOutboundContainerScreen.tsx create mode 100644 src/screens/Replenishment/ReplenishmentPickQuantityScreen.tsx create mode 100644 src/screens/Replenishment/ReplenishmentProductScreen.tsx create mode 100644 src/screens/Replenishment/ReplenishmentStagingLocationScreen.tsx create mode 100644 src/screens/Replenishment/mock-data.ts create mode 100644 src/screens/Replenishment/styles.ts create mode 100644 src/screens/Replenishment/types.ts diff --git a/src/Main.tsx b/src/Main.tsx index 5df88e80..55907896 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -4,9 +4,9 @@ import { NavigationContainer } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import React, { Component } from 'react'; import { SafeAreaView } from 'react-native'; -import { Provider } from 'react-native-paper'; import SplashScreen from 'react-native-splash-screen'; import { connect } from 'react-redux'; +import { Providers } from './components/Providers'; import FullScreenLoadingIndicator from './components/FullScreenLoadingIndicator'; import showPopup from './components/Popup'; import { appConfig } from './constants'; @@ -70,7 +70,11 @@ import PickingPickOutboundContainerScreen from './screens/Picking/PickingPickOut import PickingPickStagingLocationScreen from './screens/Picking/PickingPickStagingLocationScreen'; import PickingMoveToStagingScreen from './screens/Picking/PickingMoveToStaging'; import PickingStagingDropScreen from './screens/Picking/PickingStagingDrop'; -import { PickingProvider } from './screens/Picking/PickingContext'; +import { ReplenishmentLocationScreen } from './screens/Replenishment/ReplenishmentLocationScreen'; +import { ReplenishmentProductScreen } from './screens/Replenishment/ReplenishmentProductScreen'; +import ReplenishmentOutboundContainerScreen from './screens/Replenishment/ReplenishmentOutboundContainerScreen'; +import ReplenishmentPickQuantityScreen from './screens/Replenishment/ReplenishmentPickQuantityScreen'; +import { ReplenishmentStagingLocationScreen } from './screens/Replenishment/ReplenishmentStagingLocationScreen'; const Stack = createStackNavigator(); export interface OwnProps { @@ -153,208 +157,223 @@ class Main extends Component { const { loggedIn } = this.props; const initialRouteName = !loggedIn ? 'Login' : 'Choose Location'; return ( - - - - - - ({ - headerRight: () => , - headerTintColor: Theme.colors.surface, - headerStyle: { - backgroundColor: Theme.colors.primary, - height: appConfig.APP_HEADER_HEIGHT - } - })} - > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + ({ + headerRight: () => , + headerTintColor: Theme.colors.surface, + headerStyle: { + backgroundColor: Theme.colors.primary, + height: appConfig.APP_HEADER_HEIGHT + } + })} + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); } } diff --git a/src/screens/Picking/PickingShortAndReasonModal.tsx b/src/components/PickingShortAndReasonModal/index.tsx similarity index 100% rename from src/screens/Picking/PickingShortAndReasonModal.tsx rename to src/components/PickingShortAndReasonModal/index.tsx diff --git a/src/components/PickingShortAndReasonModal/styles.ts b/src/components/PickingShortAndReasonModal/styles.ts new file mode 100644 index 00000000..f4e765cc --- /dev/null +++ b/src/components/PickingShortAndReasonModal/styles.ts @@ -0,0 +1,55 @@ +import { StyleSheet } from 'react-native'; +import Theme from '../../utils/Theme'; + +export default StyleSheet.create({ + modalOverlay: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + width: '100%', + height: '100%', + backgroundColor: 'rgba(0, 0, 0, 0.5)' + }, + modalContent: { + width: '90%', + backgroundColor: 'white', + padding: Theme.spacing.large, + borderRadius: Theme.roundness * 2 + }, + modalSurface: { + width: '90%', + borderRadius: 8, + padding: 16, + elevation: 4, + backgroundColor: 'white' + }, + modalHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 8 + }, + modalContainer: { + position: 'absolute', + top: 0, + bottom: 0, + left: 0, + right: 0 + }, + modalTitleText: { + fontSize: 18, + fontWeight: 'bold', + color: Theme.colors.text, + flexShrink: 1 + }, + modalDescription: { + fontSize: 14, + color: Theme.colors.text, + marginBottom: Theme.spacing.medium + }, + dialogActions: { + flexDirection: 'row', + justifyContent: 'flex-end', + marginTop: Theme.spacing.medium + } +}); diff --git a/src/screens/Picking/ProductDetails.tsx b/src/components/ProductDetails/index.tsx similarity index 100% rename from src/screens/Picking/ProductDetails.tsx rename to src/components/ProductDetails/index.tsx diff --git a/src/components/ProductDetails/styles.ts b/src/components/ProductDetails/styles.ts new file mode 100644 index 00000000..f6741d80 --- /dev/null +++ b/src/components/ProductDetails/styles.ts @@ -0,0 +1,40 @@ +import { StyleSheet } from 'react-native'; +import Theme from '../../utils/Theme'; + +export default StyleSheet.create({ + productDetails: { + backgroundColor: Theme.colors.surface, + padding: Theme.spacing.large, + display: 'flex', + flexDirection: 'column' + }, + headerRow: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center' + }, + chipDefault: { + height: 28, + justifyContent: 'flex-start', + borderRadius: 4, + alignItems: 'center' + }, + chipText: { + fontSize: 12, + color: Theme.colors.text + }, + fontBold: { + fontWeight: 'bold' + }, + title: { + fontSize: 18, + color: Theme.colors.text, + fontWeight: 'bold' + }, + divider: { + marginVertical: Theme.spacing.small + }, + marginTopSmall: { + marginTop: Theme.spacing.small + } +}); diff --git a/src/components/Providers.tsx b/src/components/Providers.tsx new file mode 100644 index 00000000..d61b79ee --- /dev/null +++ b/src/components/Providers.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { Provider } from 'react-native-paper'; + +import { PickingProvider } from '../screens/Picking/PickingContext'; +import { ReplenishmentProvider } from '../screens/Replenishment/ReplenishmentContext'; +import Theme from '../utils/Theme'; + +export function Providers({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + + ); +} diff --git a/src/hooks/useInputFocus.ts b/src/hooks/useInputFocus.ts new file mode 100644 index 00000000..d03472f9 --- /dev/null +++ b/src/hooks/useInputFocus.ts @@ -0,0 +1,19 @@ +import { useIsFocused } from '@react-navigation/native'; +import * as React from 'react'; +import { TextInput } from 'react-native'; +import { INPUT_FOCUS_DELAY_TIME_IN_MS } from '../constants'; + +export function useInputFocus(inputRef: React.RefObject, callback?: () => void) { + const isFocused = useIsFocused(); + + React.useEffect(() => { + if (!isFocused) { + return; + } + + callback?.(); + + const t = setTimeout(() => inputRef.current?.focus(), INPUT_FOCUS_DELAY_TIME_IN_MS); + return () => clearTimeout(t); + }, [isFocused, inputRef, callback]); +} diff --git a/src/redux/sagas/others.ts b/src/redux/sagas/others.ts index 17a41953..b84d907f 100644 --- a/src/redux/sagas/others.ts +++ b/src/redux/sagas/others.ts @@ -23,9 +23,10 @@ function* getReasonCodes(action: any) { if (action.callback) { yield call(action.callback, { error: true, - errorMessage: error.message + errorMessage: error.message ?? 'Failed To Load Reason Codes' }); } + yield put(hideScreenLoading()); } } diff --git a/src/screens/Dashboard/dashboardData.ts b/src/screens/Dashboard/dashboardData.ts index 6d1bf07a..4b1f7bd2 100644 --- a/src/screens/Dashboard/dashboardData.ts +++ b/src/screens/Dashboard/dashboardData.ts @@ -73,6 +73,13 @@ const dashboardEntries: DashboardEntry[] = [ icon: IconReceiving, navigationScreenName: 'InboundOrderList' }, + { + key: 'replenishment', + screenName: 'Replenishment', + entryDescription: 'Manage inventory replenishment tasks', + icon: IconProducts, + navigationScreenName: 'ReplenishmentPickingLocation' + }, { key: 'putawayCandidates', screenName: 'Putaway Candidates', diff --git a/src/screens/Picking/PickingPickLocationScreen.tsx b/src/screens/Picking/PickingPickLocationScreen.tsx index 8f1ccb83..9ee80fac 100644 --- a/src/screens/Picking/PickingPickLocationScreen.tsx +++ b/src/screens/Picking/PickingPickLocationScreen.tsx @@ -3,10 +3,10 @@ import * as React from 'react'; import { Alert, TextInput, View } from 'react-native'; import { Divider, TextInput as PaperTextInput, Paragraph, Subheading } from 'react-native-paper'; +import { ProductDetails } from '../../components/ProductDetails'; import { HYPHEN, INPUT_FOCUS_DELAY_TIME_IN_MS } from '../../constants'; import { navigate } from '../../NavigationService'; import { usePickingContext } from './PickingContext'; -import { ProductDetails } from './ProductDetails'; import styles from './styles'; export default function PickingPickLocationScreen() { diff --git a/src/screens/Picking/PickingPickOutboundContainerScreen.tsx b/src/screens/Picking/PickingPickOutboundContainerScreen.tsx index f2d2e873..0be0abf9 100644 --- a/src/screens/Picking/PickingPickOutboundContainerScreen.tsx +++ b/src/screens/Picking/PickingPickOutboundContainerScreen.tsx @@ -3,11 +3,11 @@ import * as React from 'react'; import { Alert, TextInput, View } from 'react-native'; import { Divider, TextInput as PaperTextInput, Paragraph, Subheading } from 'react-native-paper'; +import { ProductDetails } from '../../components/ProductDetails'; import { INPUT_FOCUS_DELAY_TIME_IN_MS } from '../../constants'; import { navigate } from '../../NavigationService'; import { ReasonCode } from '../../types/picking'; import { usePickingContext } from './PickingContext'; -import { ProductDetails } from './ProductDetails'; import styles from './styles'; type PickingPickOutboundContainerScreenProps = RouteProp< @@ -80,7 +80,7 @@ export default function PickingPickOutboundContainerScreen() { if (!currentTask) { Alert.alert('Error', 'No current pick task available.'); - setOutboundContainerId(''); + navigate('Dashboard'); return; } diff --git a/src/screens/Picking/PickingPickProductScreen.tsx b/src/screens/Picking/PickingPickProductScreen.tsx index 3fe4b152..5569fdf3 100644 --- a/src/screens/Picking/PickingPickProductScreen.tsx +++ b/src/screens/Picking/PickingPickProductScreen.tsx @@ -3,10 +3,10 @@ import * as React from 'react'; import { Alert, TextInput, View } from 'react-native'; import { Divider, TextInput as PaperTextInput, Paragraph, Subheading } from 'react-native-paper'; +import { ProductDetails } from '../../components/ProductDetails'; import { HYPHEN, INPUT_FOCUS_DELAY_TIME_IN_MS } from '../../constants'; import { navigate } from '../../NavigationService'; import { usePickingContext } from './PickingContext'; -import { ProductDetails } from './ProductDetails'; import styles from './styles'; export default function PickingPickProductScreen() { diff --git a/src/screens/Picking/PickingPickQuantityScreen.tsx b/src/screens/Picking/PickingPickQuantityScreen.tsx index 24e41f5f..78c457cb 100644 --- a/src/screens/Picking/PickingPickQuantityScreen.tsx +++ b/src/screens/Picking/PickingPickQuantityScreen.tsx @@ -4,13 +4,13 @@ import { Alert, TextInput, View } from 'react-native'; import { Button, Divider, TextInput as PaperTextInput, Paragraph, Subheading } from 'react-native-paper'; import { useDispatch } from 'react-redux'; +import PickingShortAndReasonModal from '../../components/PickingShortAndReasonModal'; +import { ProductDetails } from '../../components/ProductDetails'; import { HYPHEN, INPUT_FOCUS_DELAY_TIME_IN_MS } from '../../constants'; import { navigate } from '../../NavigationService'; import { getReasonCodesAction } from '../../redux/actions/others'; import { ReasonCode } from '../../types/picking'; import { usePickingContext } from './PickingContext'; -import PickingShortAndReasonModal from './PickingShortAndReasonModal'; -import { ProductDetails } from './ProductDetails'; import styles from './styles'; export default function PickingPickQuantityScreen() { diff --git a/src/screens/Picking/PickingPickStagingLocationScreen.tsx b/src/screens/Picking/PickingPickStagingLocationScreen.tsx index 2284bcea..84550b8e 100644 --- a/src/screens/Picking/PickingPickStagingLocationScreen.tsx +++ b/src/screens/Picking/PickingPickStagingLocationScreen.tsx @@ -3,10 +3,10 @@ import * as React from 'react'; import { Alert, TextInput, View } from 'react-native'; import { Divider, TextInput as PaperTextInput, Paragraph, Subheading } from 'react-native-paper'; +import { ProductDetails } from '../../components/ProductDetails'; import { INPUT_FOCUS_DELAY_TIME_IN_MS } from '../../constants'; import { navigate } from '../../NavigationService'; import { usePickingContext } from './PickingContext'; -import { ProductDetails } from './ProductDetails'; import styles from './styles'; export default function PickingPickStagingLocationScreen() { diff --git a/src/screens/Picking/styles.ts b/src/screens/Picking/styles.ts index 008b98c7..b2073462 100644 --- a/src/screens/Picking/styles.ts +++ b/src/screens/Picking/styles.ts @@ -51,28 +51,6 @@ export default StyleSheet.create({ fontSize: 14 }, - productDetails: { - backgroundColor: Theme.colors.surface, - padding: Theme.spacing.large, - display: 'flex', - flexDirection: 'column' - }, - headerRow: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center' - }, - chipDefault: { - height: 28, - justifyContent: 'flex-start', - borderRadius: 4, - alignItems: 'center' - }, - chipText: { - fontSize: 12, - color: Theme.colors.text - }, - title: { fontSize: 18, color: Theme.colors.text, @@ -109,91 +87,34 @@ export default StyleSheet.create({ marginRight: Theme.spacing.small }, - modalOverlay: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - width: '100%', - height: '100%', - backgroundColor: 'rgba(0, 0, 0, 0.5)' - }, - modalContent: { - width: '90%', - backgroundColor: 'white', - padding: Theme.spacing.large, - borderRadius: Theme.roundness * 2 - }, - modalSurface: { - width: '90%', - borderRadius: 8, - padding: 16, - elevation: 4, - backgroundColor: 'white' - }, - modalHeader: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - marginBottom: 8 - }, - modalContainer: { - position: 'absolute', - top: 0, - bottom: 0, - left: 0, - right: 0 - }, - centeredView: { - flex: 1, - justifyContent: 'center', - alignItems: 'center' - }, - modalTitleText: { - fontSize: 18, - fontWeight: 'bold', - color: Theme.colors.text, - flexShrink: 1 - }, - modalDescription: { - fontSize: 14, - color: Theme.colors.text, - marginBottom: Theme.spacing.medium - }, - - /** Dialog & actions **/ - dialogActions: { - flexDirection: 'row', - justifyContent: 'flex-end', - marginTop: Theme.spacing.medium - }, - dialogActionButton: { - marginLeft: Theme.spacing.small - }, - dialogSuggestButton: { - marginVertical: Theme.spacing.medium - }, - dropdownContainer: { - width: '50%', - alignSelf: 'flex-end' - }, flex1: { flex: 1 }, fullWidth: { width: '100%' }, - actionsWrapper: { - padding: Theme.spacing.medium, - alignItems: 'center', - borderTopWidth: 1, - borderTopColor: '#E0E0E0', - backgroundColor: 'white' - }, itemCard: { padding: Theme.spacing.large, backgroundColor: 'white', marginBottom: Theme.spacing.small, borderRadius: Theme.roundness, elevation: 2 + }, + + productDetails: { + backgroundColor: Theme.colors.surface, + padding: Theme.spacing.large, + display: 'flex', + flexDirection: 'column' + }, + chipDefault: { + height: 28, + justifyContent: 'flex-start', + borderRadius: 4, + alignItems: 'center' + }, + chipText: { + fontSize: 12, + color: Theme.colors.text } }); diff --git a/src/screens/Replenishment/ReplenishmentContext.tsx b/src/screens/Replenishment/ReplenishmentContext.tsx new file mode 100644 index 00000000..095411f1 --- /dev/null +++ b/src/screens/Replenishment/ReplenishmentContext.tsx @@ -0,0 +1,131 @@ +/* eslint-disable no-unused-vars */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import React, { createContext, ReactNode, useContext, useState } from 'react'; +import { useDispatch } from 'react-redux'; + +import { DUMMY_REPLENISHMENT_TASKS } from './mock-data'; +import { ReplenishmentTask } from './types'; + +type ReplenishmentContextType = { + allTasks: ReplenishmentTask[]; + tasksCount: number; + currentTask?: ReplenishmentTask; + currentTaskIndex: number; + startReplenishment: (callback: (response: { errorMessage?: string }) => void) => void; + goToNextTask: () => void; + pickCurrentTask: (outboundContainerId: string, callback: (response: { errorMessage?: string }) => void) => void; + shortPickTask: ( + outboundContainerId: string, + parsedQuantityPicked: number, + callback: (response: { errorMessage?: string }) => void, + reasonCodeName?: string + ) => void; + revalidateCurrentTask: (callback: (revalidatedTask?: ReplenishmentTask) => void) => void; + revalidateTasksForOrder: (orderId: string, callback: () => void) => void; + dropCurrentTaskAtStagingLocation: ( + task: ReplenishmentTask, + callback: (response: { errorMessage?: string }) => void + ) => void; + resetReplenishmentSession: () => void; +}; + +const ReplenishmentContext = createContext(undefined); + +export function ReplenishmentProvider({ children }: { children: ReactNode }) { + const dispatch = useDispatch(); + + // @ts-ignore + const [allTasks, setAllTasks] = useState(DUMMY_REPLENISHMENT_TASKS); + const [currentTaskIndex, setCurrentTaskIndex] = useState(0); + const tasksCount = allTasks.length; + const currentTask = DUMMY_REPLENISHMENT_TASKS[0]; + // const currentTask = tasksCount > 0 ? allTasks[currentTaskIndex] : undefined; + + const startReplenishment = (callback: (response: { errorMessage?: string }) => void) => { + // TODO: Implement the logic for starting the replenishment process + // dispatch(startReplenishmentProcess(currentTask.id, callback)); + }; + + const pickCurrentTask = (outboundContainerId: string, callback: (response: { errorMessage?: string }) => void) => { + // TODO: Implement the logic for picking the current task + // dispatch(pickCurrentTask(currentTask.id, outboundContainerId, callback)); + }; + + const shortPickTask = ( + outboundContainerId: string, + parsedQuantityPicked: number, + callback: (response: { errorMessage?: string }) => void, + reasonCodeName?: string + ) => { + // TODO: Implement the logic for short picking the current task + // dispatch(shortPickTask(currentTask.id, outboundContainerId, parsedQuantityPicked, callback, reasonCodeName)); + }; + + const revalidateCurrentTask = (callback: (revalidatedTask?: ReplenishmentTask) => void) => { + // TODO: Implement the logic for revalidating the current task + // dispatch(getTaskById(currentTask.id, ({ response }) => { + // if (response.errorCode || !response.data) { + // Alert.alert('Failed To Revalidate', 'Could not revalidate the current task.'); + // return; + // } + // + // const revalidatedTask = response.data; + // setAllTasks((prevTasks) => prevTasks.map((task, index) => (index === currentTaskIndex ? revalidatedTask : task))); + // callback?.(revalidatedTask); + // }); + }; + + const revalidateTasksForOrder = (orderId: string, callback: () => void) => { + // TODO: Implement the logic for revalidating all tasks for the order + // dispatch(revalidateTasksForOrderAction(orderId, ({ response }) => { + // TODO: Handle response and update tasks accordingly + //})); + }; + + const dropCurrentTaskAtStagingLocation = ( + task: ReplenishmentTask, + callback: (response: { errorMessage?: string }) => void + ) => { + // TODO: Implement the logic for dropping the current task at the staging location + // dispatch(dropTaskAtStagingLocation(task.outboundContainer.id, task.stagingLocation.id, callback)); + }; + + const resetReplenishmentSession = () => { + setAllTasks([]); + setCurrentTaskIndex(0); + }; + + const goToNextTask = () => { + setCurrentTaskIndex((prevIndex) => prevIndex + 1); + }; + + return ( + + {children} + + ); +} + +export function useReplenishmentContext() { + const context = useContext(ReplenishmentContext); + if (!context) { + throw new Error('useReplenishmentContext must be used within a ReplenishmentProvider'); + } + return context; +} diff --git a/src/screens/Replenishment/ReplenishmentLocationScreen.tsx b/src/screens/Replenishment/ReplenishmentLocationScreen.tsx new file mode 100644 index 00000000..36383ddc --- /dev/null +++ b/src/screens/Replenishment/ReplenishmentLocationScreen.tsx @@ -0,0 +1,111 @@ +/* eslint-disable no-unused-vars */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import React from 'react'; +import { Divider, TextInput as PaperTextInput, Paragraph, Subheading } from 'react-native-paper'; + +import { Alert, TextInput, View } from 'react-native'; +import { ProductDetails } from '../../components/ProductDetails'; +import { HYPHEN } from '../../constants'; +import { useInputFocus } from '../../hooks/useInputFocus'; +import { navigate } from '../../NavigationService'; +import { DUMMY_REPLENISHMENT } from './mock-data'; +import { useReplenishmentContext } from './ReplenishmentContext'; +import styles from './styles'; + +export function ReplenishmentLocationScreen() { + const { currentTask, currentTaskIndex, tasksCount, startReplenishment } = useReplenishmentContext(); + + const inputRef = React.useRef(null); + const [locationBarcode, setLocationBarcode] = React.useState(''); + + // Focus input when screen is focused + useInputFocus(inputRef); + + if (!currentTask) { + Alert.alert('No Replenishment Task', 'There is no current replenishment task available. Try again later.', [ + { + text: 'OK', + onPress: () => { + navigate('Dashboard'); + } + } + ]); + return null; + } + + function handleSubmit() { + const isValid = locationBarcode === currentTask?.location?.locationNumber; + + if (!isValid) { + Alert.alert( + 'Invalid Barcode', + `Incorrect location scanned. Expected: ${currentTask?.location?.locationNumber}. Try again.` + ); + setLocationBarcode(''); + return; + } + + // startReplenishment((response) => { + // if ('errorMessage' in response) { + // Alert.alert('Error', response.errorMessage); + // setLocationBarcode(''); + // return; + // } + + // navigate('ReplenishmentProduct'); + // }); + + navigate('ReplenishmentProduct'); + } + + return ( + // @ts-ignore + + + + + {DUMMY_REPLENISHMENT.product.productCode} + + + {`${currentTaskIndex + 1} / ${tasksCount}`} + + + + + + + + + + + + + Scan Pick Location Barcode + + Point your barcode scanner at the pick location barcode or type the code manually. + + + + + + ); +} diff --git a/src/screens/Replenishment/ReplenishmentOutboundContainerScreen.tsx b/src/screens/Replenishment/ReplenishmentOutboundContainerScreen.tsx new file mode 100644 index 00000000..0e7141dd --- /dev/null +++ b/src/screens/Replenishment/ReplenishmentOutboundContainerScreen.tsx @@ -0,0 +1,181 @@ +/* eslint-disable no-unused-vars */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { RouteProp, useRoute } from '@react-navigation/native'; +import * as React from 'react'; +import { Alert, TextInput, View } from 'react-native'; +import { Divider, TextInput as PaperTextInput, Paragraph, Subheading } from 'react-native-paper'; + +import { ProductDetails } from '../../components/ProductDetails'; +import { useInputFocus } from '../../hooks/useInputFocus'; +import { navigate } from '../../NavigationService'; +import { ReasonCode } from '../../types/picking'; +import { useReplenishmentContext } from './ReplenishmentContext'; +import styles from './styles'; + +type ReplenishmentOutboundContainerScreenProps = RouteProp< + { ReplenishmentOutboundContainer: { reasonCode?: ReasonCode; quantityPicked?: string } }, + 'ReplenishmentOutboundContainer' +>; + +export default function ReplenishmentOutboundContainerScreen() { + const { + currentTask, + pickCurrentTask, + shortPickTask, + currentTaskIndex, + tasksCount, + revalidateCurrentTask, + goToNextTask, + revalidateTasksForOrder + } = useReplenishmentContext(); + const { params } = useRoute(); + const parsedQuantityPicked = params?.quantityPicked ? Number(params.quantityPicked) : undefined; + + const inputRef = React.useRef(null); + const [outboundContainerId, setOutboundContainerId] = React.useState(''); + + // Focus input when screen is focused + useInputFocus(inputRef); + + if (!currentTask) { + Alert.alert('No Pick Task', 'There is no current pick task available. Try again later.', [ + { + text: 'OK', + onPress: () => { + navigate('Dashboard'); + } + } + ]); + return null; + } + + function revalidateTaskAndProceed() { + revalidateCurrentTask((revalidatedTask) => { + if (!revalidatedTask) { + Alert.alert('Error', 'Failed to revalidate the current pick task after picking.'); + return; + } + + if (currentTaskIndex + 1 >= tasksCount) { + // Last Task -> Navigate to staging location drop + Alert.alert('All Picks Complete', 'You have completed all picks. Proceeding to staging location drop.', [ + { + text: 'OK', + onPress: () => navigate('ReplenishmentStagingLocation') + } + ]); + } else { + // More Tasks -> Start over with next pick task + goToNextTask(); + navigate('ReplenishmentPickingLocation'); + } + }); + } + + function handleSubmit() { + if (!outboundContainerId) { + Alert.alert('Missing Input', 'Please scan or enter a valid Outbound Container ID.'); + setOutboundContainerId(''); + return; + } + + if (!currentTask) { + Alert.alert('Error', 'No current pick task available.'); + navigate('Dashboard'); + return; + } + + if (parsedQuantityPicked !== undefined && parsedQuantityPicked < currentTask.quantityRequired) { + // Handle Short Pick + shortPickTask( + outboundContainerId, + parsedQuantityPicked, + (response) => { + if ('errorMessage' in response) { + Alert.alert('Short Pick Error', response.errorMessage); + setOutboundContainerId(''); + return; + } + + if (params?.reasonCode?.id) { + // Revalidate all tasks for the order to get updated pick tasks + revalidateTasksForOrder(currentTask.orderId, () => { + navigate('ReplenishmentPickingLocation'); + }); + } else { + revalidateTaskAndProceed(); + } + }, + params?.reasonCode?.name + ); + return; + } + + // pickCurrentTask(outboundContainerId, ({ errorMessage }) => { + // if (errorMessage) { + // Alert.alert('Pick Error', errorMessage); + // setOutboundContainerId(''); + // return; + // } + + // revalidateTaskAndProceed(); + // }); + + // TODO: Temporarily bypass picking for outbound container scanning flow + navigate('ReplenishmentStagingLocation'); + } + + return ( + + + + + {currentTask.product.productCode} + + + {`${currentTaskIndex + 1} / ${tasksCount}`} + + + + + + + + + + + + + Scan Outbound Container + + Point your barcode scanner at the outbound container or type the code manually. + + + + + + ); +} diff --git a/src/screens/Replenishment/ReplenishmentPickQuantityScreen.tsx b/src/screens/Replenishment/ReplenishmentPickQuantityScreen.tsx new file mode 100644 index 00000000..e693261a --- /dev/null +++ b/src/screens/Replenishment/ReplenishmentPickQuantityScreen.tsx @@ -0,0 +1,146 @@ +import React, { useEffect, useState } from 'react'; +import { Alert, TextInput, View } from 'react-native'; +import { Button, Divider, TextInput as PaperTextInput, Paragraph, Subheading } from 'react-native-paper'; +import { useDispatch } from 'react-redux'; + +import PickingShortAndReasonModal from '../../components/PickingShortAndReasonModal'; +import { ProductDetails } from '../../components/ProductDetails'; +import { HYPHEN } from '../../constants'; +import { useInputFocus } from '../../hooks/useInputFocus'; +import { navigate } from '../../NavigationService'; +import { getReasonCodesAction } from '../../redux/actions/others'; +import { ReasonCode } from '../../types/picking'; +import { DUMMY_REPLENISHMENT } from './mock-data'; +import { useReplenishmentContext } from './ReplenishmentContext'; +import styles from './styles'; + +export default function ReplenishmentPickQuantityScreen() { + const { currentTask, currentTaskIndex, tasksCount } = useReplenishmentContext(); + const dispatch = useDispatch(); + const inputRef = React.useRef(null); + + const [quantityPicked, setQuantityPicked] = useState(''); + const [reasonCodes, setReasonCodes] = useState([]); + const [selectedReasonCode, setSelectedReasonCode] = useState(undefined); + const [isShortModalVisible, setIsShortModalVisible] = useState(false); + + // Focus input when screen is focused + useInputFocus(inputRef); + + useEffect(() => { + dispatch( + getReasonCodesAction('PICKING_SHORTAGE', (data: any) => { + if ('errorMessage' in data) { + Alert.alert('Error', data.errorMessage); + return; + } + + setReasonCodes(data); + }) + ); + }, [dispatch]); + + if (!currentTask) { + Alert.alert('No Replenishment Task', 'There is no current replenishment task available. Try again later.'); + navigate('Dashboard'); + return null; + } + + function handleSubmit() { + const qty = Number(quantityPicked); + const isValid = !isNaN(qty) && qty >= 0 && currentTask?.quantityRequired; + + if (!isValid) { + Alert.alert('Invalid Quantity', 'Incorrect quantity picked. Please try again.'); + return; + } + + const qtyRemaining = currentTask?.quantityRequired - (currentTask?.quantityPicked || 0); + const isFullPicked = qty === qtyRemaining; + + if (qty > qtyRemaining) { + Alert.alert('Invalid Quantity', `Picked quantity cannot exceed remaining quantity to pick (${qtyRemaining}).`); + return; + } + + if (isFullPicked) { + navigate('ReplenishmentOutboundContainer'); + return; + } + + setIsShortModalVisible(true); + } + + function handleConfirmShort(reasonCode: ReasonCode | undefined) { + setIsShortModalVisible(false); + + navigate('PickingPickOutboundContainer', { reasonCode, quantityPicked }); + } + + return ( + <> + {/* @ts-ignore */} + + + + + {currentTask.product.productCode} + + + {`${currentTaskIndex + 1} / ${tasksCount}`} + + + + + + + + + + + + + Enter Quantity Picked + + Please enter the quantity of the product that you have picked from the location. + + + + + + + + + setIsShortModalVisible(false)} + onConfirm={handleConfirmShort} + /> + + ); +} diff --git a/src/screens/Replenishment/ReplenishmentProductScreen.tsx b/src/screens/Replenishment/ReplenishmentProductScreen.tsx new file mode 100644 index 00000000..6cab914b --- /dev/null +++ b/src/screens/Replenishment/ReplenishmentProductScreen.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import { Divider, TextInput as PaperTextInput, Paragraph, Subheading } from 'react-native-paper'; + +import { Alert, TextInput, View } from 'react-native'; +import { ProductDetails } from '../../components/ProductDetails'; +import { HYPHEN } from '../../constants'; +import { useInputFocus } from '../../hooks/useInputFocus'; +import { navigate } from '../../NavigationService'; +import { DUMMY_REPLENISHMENT } from './mock-data'; +import { useReplenishmentContext } from './ReplenishmentContext'; +import styles from './styles'; + +export function ReplenishmentProductScreen() { + const { currentTask, currentTaskIndex, tasksCount } = useReplenishmentContext(); + + const inputRef = React.useRef(null); + const [productBarcode, setProductBarcode] = React.useState(''); + + useInputFocus(inputRef); + + if (!currentTask) { + Alert.alert('No Replenishment Task', 'There is no current replenishment task available. Try again later.'); + navigate('Dashboard'); + return null; + } + + function handleSubmit() { + // TODO: Replace with actual barcode validation logic + const isValid = true; + + if (!isValid) { + Alert.alert( + 'Invalid Barcode', + `Incorrect product scanned. Expected: ${currentTask?.product?.productCode}. Try again.` + ); + setProductBarcode(''); + return; + } + + // TODO: Implement the logic for proceeding to the next step + navigate('ReplenishmentPickQuantity'); + } + + return ( + // @ts-ignore + + + + + {DUMMY_REPLENISHMENT.product.productCode} + + + {`${currentTaskIndex + 1} / ${tasksCount}`} + + + + + + + + + + + + + Scan Pick Product Barcode + + Point your barcode scanner at the pick product barcode or type the code manually. + + + + + + ); +} diff --git a/src/screens/Replenishment/ReplenishmentStagingLocationScreen.tsx b/src/screens/Replenishment/ReplenishmentStagingLocationScreen.tsx new file mode 100644 index 00000000..0285f961 --- /dev/null +++ b/src/screens/Replenishment/ReplenishmentStagingLocationScreen.tsx @@ -0,0 +1,137 @@ +import * as React from 'react'; +import { Alert, TextInput, View } from 'react-native'; +import { Divider, TextInput as PaperTextInput, Paragraph, Subheading } from 'react-native-paper'; + +import { ProductDetails } from '../../components/ProductDetails'; +import { useInputFocus } from '../../hooks/useInputFocus'; +import { navigate } from '../../NavigationService'; +import { useReplenishmentContext } from './ReplenishmentContext'; +import styles from './styles'; + +export function ReplenishmentStagingLocationScreen() { + const { allTasks, dropCurrentTaskAtStagingLocation, resetReplenishmentSession } = useReplenishmentContext(); + + const inputRef = React.useRef(null); + const [stagingLocationNumber, setStagingLocationNumber] = React.useState(''); + const [currentUniqueIndex, setCurrentUniqueIndex] = React.useState(0); + + const uniqueTasks = React.useMemo( + () => Array.from(new Map(allTasks.map((task) => [task.outboundContainer?.id, task])).values()), + [allTasks] + ); + + const currentTask = uniqueTasks[currentUniqueIndex]; + + useInputFocus(inputRef); + + if (!currentTask) { + Alert.alert('Error', 'No current replenishment task available.'); + navigate('Dashboard'); + return null; + } + + function handleSubmit() { + if (!stagingLocationNumber) { + Alert.alert('Missing Input', 'Please scan or enter a valid Staging Location ID.'); + setStagingLocationNumber(''); + return; + } + + const expected = currentTask.stagingLocation?.locationNumber; + + if (!expected || stagingLocationNumber !== expected) { + Alert.alert( + 'Invalid Staging Location', + `Expected: ${expected ?? '-'}, but got: ${stagingLocationNumber}. Please try again.` + ); + setStagingLocationNumber(''); + return; + } + + dropCurrentTaskAtStagingLocation(currentTask, (response) => { + if ('errorMessage' in response) { + Alert.alert('Error', response.errorMessage); + setStagingLocationNumber(''); + return; + } + + const nextIndex = currentUniqueIndex + 1; + if (nextIndex < uniqueTasks.length) { + Alert.alert('Success', 'Staging Location confirmed. Proceeding to the next container.', [ + { + text: 'OK', + onPress: () => { + // Proceed to next unique task + setCurrentUniqueIndex(nextIndex); + setStagingLocationNumber(''); + } + } + ]); + } else { + Alert.alert('Picking Session Complete', 'You have completed all staging confirmations.', [ + { + text: 'OK', + onPress: () => { + resetReplenishmentSession(); + navigate('Dashboard'); + } + } + ]); + } + }); + } + + return ( + + + + + {currentTask.product.productCode} + + + {`${currentUniqueIndex + 1} / ${uniqueTasks.length}`} + + + + + + + + + + + + + Scan Staging Location + + Point your barcode scanner at the staging location or type the ID manually. + + + + + + ); +} diff --git a/src/screens/Replenishment/mock-data.ts b/src/screens/Replenishment/mock-data.ts new file mode 100644 index 00000000..8ac18878 --- /dev/null +++ b/src/screens/Replenishment/mock-data.ts @@ -0,0 +1,32 @@ +export const DUMMY_REPLENISHMENT = { + id: 'rep-001', + product: { + id: 'prod-001', + productCode: 'PROD-001', + name: 'Sample Product' + } +}; + +export const DUMMY_REPLENISHMENT_TASKS = [ + { + id: 'task-001', + location: { + id: 'loc-001', + locationNumber: 'LOC-001', + name: 'Sample Location' + }, + quantityPicked: 0, + quantityRequired: 10, + product: { + id: 'prod-001', + productCode: 'PROD-001', + name: 'Sample Product' + }, + replenishment: DUMMY_REPLENISHMENT, + outboundContainer: { + id: 'container-001', + locationNumber: 'CONT-001', + name: 'Outbound Container 1' + } + } +]; diff --git a/src/screens/Replenishment/styles.ts b/src/screens/Replenishment/styles.ts new file mode 100644 index 00000000..ebb303f4 --- /dev/null +++ b/src/screens/Replenishment/styles.ts @@ -0,0 +1,61 @@ +import { StyleSheet } from 'react-native'; +import Theme from '../../utils/Theme'; + +export default StyleSheet.create({ + rootWrapper: { + backgroundColor: Theme.colors.surface, + padding: Theme.spacing.large, + display: 'flex', + flexDirection: 'column' + }, + wrapperWithPadding: { + padding: Theme.spacing.medium + }, + headerRow: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center' + }, + chipDefault: { + height: 28, + justifyContent: 'flex-start', + borderRadius: 4, + alignItems: 'center' + }, + chipText: { + fontSize: 12, + color: Theme.colors.text + }, + + title: { + fontSize: 18, + color: Theme.colors.text, + fontWeight: 'bold' + }, + subheading: { + fontSize: 16, + color: Theme.colors.text, + fontWeight: 'bold' + }, + caption: { fontSize: 12 }, + paragraph: { + fontSize: 14, + color: Theme.colors.text, + fontWeight: 'normal' + }, + fontBold: { + fontWeight: 'bold' + }, + marginTop: { + marginTop: Theme.spacing.medium + }, + marginTopSmall: { + marginTop: Theme.spacing.small + }, + marginBottom: { + marginBottom: Theme.spacing.medium + }, + divider: { + marginVertical: Theme.spacing.small + } +}); diff --git a/src/screens/Replenishment/types.ts b/src/screens/Replenishment/types.ts new file mode 100644 index 00000000..c845512c --- /dev/null +++ b/src/screens/Replenishment/types.ts @@ -0,0 +1,49 @@ +import { Container } from '../../data/container/Shipment'; +import Location from '../../data/location/Location'; +import InventoryItem from '../../data/picklist/InventoryItem'; +import Product from '../../data/product/Product'; + +export enum ReplenishmentTaskStatus { + PENDING = 'PENDING', + PICKING = 'PICKING', + PICKED = 'PICKED', + STAGED = 'STAGED' +} + +export type ReplenishmentTask = { + id: string; + identifier: string; + + orderId: string; + orderNumber: string; + orderStatus: string; + orderType: string; + + facility?: Location; + location?: Location; + outboundContainer?: Container | null; + stagingLocation?: Location | null; + + product: Product; + inventoryItem: InventoryItem; + + quantityRequired: number; + quantityPicked: number; + + requestedBy?: string | null; + assignee?: string | null; + pickedBy?: string | null; + stagedBy?: string | null; + + priority?: number; + reasonCode?: string | null; + status: ReplenishmentTaskStatus; + + dateRequested?: string | null; + dateAssigned?: string | null; + dateStarted?: string | null; + datePicked?: string | null; + dateStaged?: string | null; + dateCreated: string; + lastUpdated: string; +}; From 3a93a0fc4b77135514d1277b3082330c1acb96d0 Mon Sep 17 00:00:00 2001 From: olewandowski1 Date: Mon, 12 Jan 2026 13:28:10 +0100 Subject: [PATCH 2/8] OBLS-381 correct label --- src/screens/Replenishment/ReplenishmentProductScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screens/Replenishment/ReplenishmentProductScreen.tsx b/src/screens/Replenishment/ReplenishmentProductScreen.tsx index 6cab914b..161f8344 100644 --- a/src/screens/Replenishment/ReplenishmentProductScreen.tsx +++ b/src/screens/Replenishment/ReplenishmentProductScreen.tsx @@ -74,7 +74,7 @@ export function ReplenishmentProductScreen() { Scan Pick Product Barcode - Point your barcode scanner at the pick product barcode or type the code manually. + Point your barcode scanner at the product barcode or type the code manually. Date: Wed, 21 Jan 2026 16:05:41 +0100 Subject: [PATCH 3/8] OBLS-381 format code --- src/Main.tsx | 94 ++++++++++++++++++++++++++-------------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/src/Main.tsx b/src/Main.tsx index e3a1a9da..17779310 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -211,13 +211,13 @@ class Main extends Component { options={{ title: 'Transfer Details' }} /> - ({ title: route.params?.subroutesScreenName })} - /> + ({ title: route.params?.subroutesScreenName })} + /> @@ -386,46 +386,46 @@ class Main extends Component { component={ReplenishmentStagingLocationScreen} options={{ title: 'Replenishment Staging Location' }} /> - - - - - - - - + + + + + + + + From b3558485b083dcd6d2438c98cd5b62f968ee1374 Mon Sep 17 00:00:00 2001 From: olewandowski1 Date: Wed, 21 Jan 2026 16:12:01 +0100 Subject: [PATCH 4/8] OBLS-381 use scanner input --- .../ReplenishmentLocationScreen.tsx | 22 ++++++------------- .../ReplenishmentOutboundContainerScreen.tsx | 22 ++++++------------- .../ReplenishmentProductScreen.tsx | 22 ++++++------------- .../ReplenishmentStagingLocationScreen.tsx | 21 ++++++------------ 4 files changed, 28 insertions(+), 59 deletions(-) diff --git a/src/screens/Replenishment/ReplenishmentLocationScreen.tsx b/src/screens/Replenishment/ReplenishmentLocationScreen.tsx index 36383ddc..9ccbceb2 100644 --- a/src/screens/Replenishment/ReplenishmentLocationScreen.tsx +++ b/src/screens/Replenishment/ReplenishmentLocationScreen.tsx @@ -1,12 +1,12 @@ /* eslint-disable no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */ import React from 'react'; -import { Divider, TextInput as PaperTextInput, Paragraph, Subheading } from 'react-native-paper'; +import { Divider, Paragraph, Subheading } from 'react-native-paper'; -import { Alert, TextInput, View } from 'react-native'; +import { Alert, View } from 'react-native'; import { ProductDetails } from '../../components/ProductDetails'; +import { ScannerInput } from '../../components/ScannerInput'; import { HYPHEN } from '../../constants'; -import { useInputFocus } from '../../hooks/useInputFocus'; import { navigate } from '../../NavigationService'; import { DUMMY_REPLENISHMENT } from './mock-data'; import { useReplenishmentContext } from './ReplenishmentContext'; @@ -15,12 +15,8 @@ import styles from './styles'; export function ReplenishmentLocationScreen() { const { currentTask, currentTaskIndex, tasksCount, startReplenishment } = useReplenishmentContext(); - const inputRef = React.useRef(null); const [locationBarcode, setLocationBarcode] = React.useState(''); - // Focus input when screen is focused - useInputFocus(inputRef); - if (!currentTask) { Alert.alert('No Replenishment Task', 'There is no current replenishment task available. Try again later.', [ { @@ -94,16 +90,12 @@ export function ReplenishmentLocationScreen() { Point your barcode scanner at the pick location barcode or type the code manually. - diff --git a/src/screens/Replenishment/ReplenishmentOutboundContainerScreen.tsx b/src/screens/Replenishment/ReplenishmentOutboundContainerScreen.tsx index 0e7141dd..d6c8c310 100644 --- a/src/screens/Replenishment/ReplenishmentOutboundContainerScreen.tsx +++ b/src/screens/Replenishment/ReplenishmentOutboundContainerScreen.tsx @@ -2,11 +2,11 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { RouteProp, useRoute } from '@react-navigation/native'; import * as React from 'react'; -import { Alert, TextInput, View } from 'react-native'; -import { Divider, TextInput as PaperTextInput, Paragraph, Subheading } from 'react-native-paper'; +import { Alert, View } from 'react-native'; +import { Divider, Paragraph, Subheading } from 'react-native-paper'; import { ProductDetails } from '../../components/ProductDetails'; -import { useInputFocus } from '../../hooks/useInputFocus'; +import { ScannerInput } from '../../components/ScannerInput'; import { navigate } from '../../NavigationService'; import { ReasonCode } from '../../types/picking'; import { useReplenishmentContext } from './ReplenishmentContext'; @@ -29,14 +29,10 @@ export default function ReplenishmentOutboundContainerScreen() { revalidateTasksForOrder } = useReplenishmentContext(); const { params } = useRoute(); - const parsedQuantityPicked = params?.quantityPicked ? Number(params.quantityPicked) : undefined; - const inputRef = React.useRef(null); + const parsedQuantityPicked = params?.quantityPicked ? Number(params.quantityPicked) : undefined; const [outboundContainerId, setOutboundContainerId] = React.useState(''); - // Focus input when screen is focused - useInputFocus(inputRef); - if (!currentTask) { Alert.alert('No Pick Task', 'There is no current pick task available. Try again later.', [ { @@ -164,16 +160,12 @@ export default function ReplenishmentOutboundContainerScreen() { Point your barcode scanner at the outbound container or type the code manually. - diff --git a/src/screens/Replenishment/ReplenishmentProductScreen.tsx b/src/screens/Replenishment/ReplenishmentProductScreen.tsx index 161f8344..1ec20ebb 100644 --- a/src/screens/Replenishment/ReplenishmentProductScreen.tsx +++ b/src/screens/Replenishment/ReplenishmentProductScreen.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import { Divider, TextInput as PaperTextInput, Paragraph, Subheading } from 'react-native-paper'; +import { Divider, Paragraph, Subheading } from 'react-native-paper'; -import { Alert, TextInput, View } from 'react-native'; +import { Alert, View } from 'react-native'; import { ProductDetails } from '../../components/ProductDetails'; +import { ScannerInput } from '../../components/ScannerInput'; import { HYPHEN } from '../../constants'; -import { useInputFocus } from '../../hooks/useInputFocus'; import { navigate } from '../../NavigationService'; import { DUMMY_REPLENISHMENT } from './mock-data'; import { useReplenishmentContext } from './ReplenishmentContext'; @@ -12,12 +12,8 @@ import styles from './styles'; export function ReplenishmentProductScreen() { const { currentTask, currentTaskIndex, tasksCount } = useReplenishmentContext(); - - const inputRef = React.useRef(null); const [productBarcode, setProductBarcode] = React.useState(''); - useInputFocus(inputRef); - if (!currentTask) { Alert.alert('No Replenishment Task', 'There is no current replenishment task available. Try again later.'); navigate('Dashboard'); @@ -77,16 +73,12 @@ export function ReplenishmentProductScreen() { Point your barcode scanner at the product barcode or type the code manually. - diff --git a/src/screens/Replenishment/ReplenishmentStagingLocationScreen.tsx b/src/screens/Replenishment/ReplenishmentStagingLocationScreen.tsx index 0285f961..772862c3 100644 --- a/src/screens/Replenishment/ReplenishmentStagingLocationScreen.tsx +++ b/src/screens/Replenishment/ReplenishmentStagingLocationScreen.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; -import { Alert, TextInput, View } from 'react-native'; -import { Divider, TextInput as PaperTextInput, Paragraph, Subheading } from 'react-native-paper'; +import { Alert, View } from 'react-native'; +import { Divider, Paragraph, Subheading } from 'react-native-paper'; import { ProductDetails } from '../../components/ProductDetails'; -import { useInputFocus } from '../../hooks/useInputFocus'; +import { ScannerInput } from '../../components/ScannerInput'; import { navigate } from '../../NavigationService'; import { useReplenishmentContext } from './ReplenishmentContext'; import styles from './styles'; @@ -11,7 +11,6 @@ import styles from './styles'; export function ReplenishmentStagingLocationScreen() { const { allTasks, dropCurrentTaskAtStagingLocation, resetReplenishmentSession } = useReplenishmentContext(); - const inputRef = React.useRef(null); const [stagingLocationNumber, setStagingLocationNumber] = React.useState(''); const [currentUniqueIndex, setCurrentUniqueIndex] = React.useState(0); @@ -22,8 +21,6 @@ export function ReplenishmentStagingLocationScreen() { const currentTask = uniqueTasks[currentUniqueIndex]; - useInputFocus(inputRef); - if (!currentTask) { Alert.alert('Error', 'No current replenishment task available.'); navigate('Dashboard'); @@ -120,16 +117,12 @@ export function ReplenishmentStagingLocationScreen() { Point your barcode scanner at the staging location or type the ID manually. - From c44fb2b6e57a43c2e6248dc1e1dcea5917afe262 Mon Sep 17 00:00:00 2001 From: olewandowski1 Date: Wed, 21 Jan 2026 16:14:17 +0100 Subject: [PATCH 5/8] OBLS-381 allow to proceed from drop to staging step --- .../ReplenishmentStagingLocationScreen.tsx | 89 ++++++++++--------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/src/screens/Replenishment/ReplenishmentStagingLocationScreen.tsx b/src/screens/Replenishment/ReplenishmentStagingLocationScreen.tsx index 772862c3..4097cc9e 100644 --- a/src/screens/Replenishment/ReplenishmentStagingLocationScreen.tsx +++ b/src/screens/Replenishment/ReplenishmentStagingLocationScreen.tsx @@ -1,3 +1,5 @@ +/* eslint-disable no-unused-vars */ +/* eslint-disable @typescript-eslint/no-unused-vars */ import * as React from 'react'; import { Alert, View } from 'react-native'; import { Divider, Paragraph, Subheading } from 'react-native-paper'; @@ -34,48 +36,51 @@ export function ReplenishmentStagingLocationScreen() { return; } - const expected = currentTask.stagingLocation?.locationNumber; - - if (!expected || stagingLocationNumber !== expected) { - Alert.alert( - 'Invalid Staging Location', - `Expected: ${expected ?? '-'}, but got: ${stagingLocationNumber}. Please try again.` - ); - setStagingLocationNumber(''); - return; - } - - dropCurrentTaskAtStagingLocation(currentTask, (response) => { - if ('errorMessage' in response) { - Alert.alert('Error', response.errorMessage); - setStagingLocationNumber(''); - return; - } - - const nextIndex = currentUniqueIndex + 1; - if (nextIndex < uniqueTasks.length) { - Alert.alert('Success', 'Staging Location confirmed. Proceeding to the next container.', [ - { - text: 'OK', - onPress: () => { - // Proceed to next unique task - setCurrentUniqueIndex(nextIndex); - setStagingLocationNumber(''); - } - } - ]); - } else { - Alert.alert('Picking Session Complete', 'You have completed all staging confirmations.', [ - { - text: 'OK', - onPress: () => { - resetReplenishmentSession(); - navigate('Dashboard'); - } - } - ]); - } - }); + navigate('ReplenishmentPickType'); + + // TODO: Implement the staging location validation and task dropping logic + // const expected = currentTask.stagingLocation?.locationNumber; + + // if (!expected || stagingLocationNumber !== expected) { + // Alert.alert( + // 'Invalid Staging Location', + // `Expected: ${expected ?? '-'}, but got: ${stagingLocationNumber}. Please try again.` + // ); + // setStagingLocationNumber(''); + // return; + // } + + // dropCurrentTaskAtStagingLocation(currentTask, (response) => { + // if ('errorMessage' in response) { + // Alert.alert('Error', response.errorMessage); + // setStagingLocationNumber(''); + // return; + // } + + // const nextIndex = currentUniqueIndex + 1; + // if (nextIndex < uniqueTasks.length) { + // Alert.alert('Success', 'Staging Location confirmed. Proceeding to the next container.', [ + // { + // text: 'OK', + // onPress: () => { + // // Proceed to next unique task + // setCurrentUniqueIndex(nextIndex); + // setStagingLocationNumber(''); + // } + // } + // ]); + // } else { + // Alert.alert('Picking Session Complete', 'You have completed all staging confirmations.', [ + // { + // text: 'OK', + // onPress: () => { + // resetReplenishmentSession(); + // navigate('Dashboard'); + // } + // } + // ]); + // } + // }); } return ( From 3128ded22940da63bcb673a9de017b94ea1061e5 Mon Sep 17 00:00:00 2001 From: olewandowski1 Date: Wed, 21 Jan 2026 16:15:48 +0100 Subject: [PATCH 6/8] OBLS-381 change path --- .../Replenishment/ReplenishmentStagingLocationScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screens/Replenishment/ReplenishmentStagingLocationScreen.tsx b/src/screens/Replenishment/ReplenishmentStagingLocationScreen.tsx index 4097cc9e..5a4e4df2 100644 --- a/src/screens/Replenishment/ReplenishmentStagingLocationScreen.tsx +++ b/src/screens/Replenishment/ReplenishmentStagingLocationScreen.tsx @@ -36,7 +36,7 @@ export function ReplenishmentStagingLocationScreen() { return; } - navigate('ReplenishmentPickType'); + navigate('ReplenishmentPickingLocation'); // TODO: Implement the staging location validation and task dropping logic // const expected = currentTask.stagingLocation?.locationNumber; From 6e24af912aacd00c6fdc47ae6cf5fe9c377769d8 Mon Sep 17 00:00:00 2001 From: olewandowski1 Date: Wed, 21 Jan 2026 16:17:28 +0100 Subject: [PATCH 7/8] OBLS-381 add replenishment to inventory dashboard entry --- src/screens/Dashboard/dashboardData.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/screens/Dashboard/dashboardData.ts b/src/screens/Dashboard/dashboardData.ts index 6d4c3edb..c18a9783 100644 --- a/src/screens/Dashboard/dashboardData.ts +++ b/src/screens/Dashboard/dashboardData.ts @@ -82,13 +82,6 @@ const dashboardEntries: DashboardEntry[] = [ icon: IconReceiving, navigationScreenName: 'InboundOrderList' }, - { - key: 'replenishment', - screenName: 'Replenishment', - entryDescription: 'Manage inventory replenishment tasks', - icon: IconProducts, - navigationScreenName: 'ReplenishmentPickingLocation' - }, { key: 'putawayCandidates', screenName: 'Putaway Candidates', @@ -152,6 +145,13 @@ const dashboardEntries: DashboardEntry[] = [ entryDescription: 'Enter a List ID to select the Cycle Count', icon: IconInventory, navigationScreenName: 'CycleCountListEntry' + }, + { + key: 'replenishment', + screenName: 'Replenishment', + entryDescription: 'Manage inventory replenishment tasks', + icon: IconProducts, + navigationScreenName: 'ReplenishmentPickingLocation' } ] }, From 0da217a8e96b239ef25645c86a46f583d04cae41 Mon Sep 17 00:00:00 2001 From: olewandowski1 Date: Wed, 21 Jan 2026 16:19:15 +0100 Subject: [PATCH 8/8] OBLS-381 clear input on redirect --- src/screens/Replenishment/ReplenishmentLocationScreen.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/screens/Replenishment/ReplenishmentLocationScreen.tsx b/src/screens/Replenishment/ReplenishmentLocationScreen.tsx index 9ccbceb2..7dcaad68 100644 --- a/src/screens/Replenishment/ReplenishmentLocationScreen.tsx +++ b/src/screens/Replenishment/ReplenishmentLocationScreen.tsx @@ -6,7 +6,7 @@ import { Divider, Paragraph, Subheading } from 'react-native-paper'; import { Alert, View } from 'react-native'; import { ProductDetails } from '../../components/ProductDetails'; import { ScannerInput } from '../../components/ScannerInput'; -import { HYPHEN } from '../../constants'; +import { EMPTY_STRING, HYPHEN } from '../../constants'; import { navigate } from '../../NavigationService'; import { DUMMY_REPLENISHMENT } from './mock-data'; import { useReplenishmentContext } from './ReplenishmentContext'; @@ -51,6 +51,7 @@ export function ReplenishmentLocationScreen() { // navigate('ReplenishmentProduct'); // }); + setLocationBarcode(EMPTY_STRING); navigate('ReplenishmentProduct'); }