Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions src/redux/sagas/products.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,21 +242,17 @@ function* stockAdjustments(action: any) {
function* getSortationDetailsSaga(action: any) {
try {
const productResponse: any = yield call(api.getProductByBarcode, action.payload.barcode);
const product = productResponse.data;

if (!product) {
throw new Error('Product not found.');
}
const product = productResponse?.data;

const location = yield select(userLocation);
if (!location || !location.id) {
if (!location?.id) {
return;
}

const tasksResponse: any = yield call(api.getPutawayTasks, location.id, product.id);
const tasks = tasksResponse?.data ?? [];

if (!tasks || tasks.length === 0) {
if (tasks.length === 0) {
yield action.callback({
error: true,
errorMessage: 'Product found but there is not putaway task for it'
Expand All @@ -266,17 +262,18 @@ function* getSortationDetailsSaga(action: any) {

yield action.callback({ product, tasks });
} catch (error: any) {
if (error.code != 401) {
yield action.callback({
error: true,
errorMessage: error.message
});
}
yield action.callback({
error: true,
// The API returns a 500 status code when the product is not found
productNotFound: error?.code === 500,
errorMessage: error.message
});
}
}

function* updateProductIdentifierSaga(action: any) {
try {
yield put(showScreenLoading('Saving...'));
const response = yield call(
api.updateProductIdentifier,
action.payload.id,
Expand All @@ -295,6 +292,8 @@ function* updateProductIdentifierSaga(action: any) {
errorMessage: error.message
});
}
} finally {
yield put(hideScreenLoading());
}
}

Expand Down
71 changes: 35 additions & 36 deletions src/screens/ProductDetails/EditBarcodeModal.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,41 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { Keyboard, Modal, View } from 'react-native';
import { Button, Headline, Paragraph, Subheading, TextInput } from 'react-native-paper';
import { Headline, Paragraph, Subheading } from 'react-native-paper';

import Button from '../../components/Button';
import { ScannerInput } from '../../components/ScannerInput';
import { EMPTY_STRING } from '../../constants';
import Theme from '../../utils/Theme';
import styles from './styles';

type EditBarcodeModalProps = {
visible: boolean;
currentBarcode?: string;
onSave: (barcode: string) => void;
onSave: (barcode: string, clear: () => void) => void;
onClose: () => void;
};

export default function EditBarcodeModal({ visible, currentBarcode, onSave, onClose }: EditBarcodeModalProps) {
const [barcode, setBarcode] = useState('');
const inputRef = useRef<any>(null);

useEffect(() => {
setBarcode('');
}, [currentBarcode]);
const [barcode, setBarcode] = useState(EMPTY_STRING);

useEffect(() => {
if (visible) {
const timer = setTimeout(() => {
inputRef.current?.focus();
}, 100);
return () => clearTimeout(timer);
setBarcode(EMPTY_STRING);
}
}, [visible]);

const handleSave = () => {
Keyboard.dismiss();
onSave(barcode, () => setBarcode(EMPTY_STRING));
};

return (
<Modal transparent animationType="slide" visible={visible} onDismiss={onClose}>
<View style={styles.modalOverlay}>
<View style={styles.modalContent}>
<Headline>Edit Barcode</Headline>
<Headline>Edit barcode</Headline>
<Paragraph style={{ marginBottom: Theme.spacing.medium }}>
You'll be able to find and scan the product using either its internal product code or its barcode, depending
You'll be able to find and scan the product using either its internal product code or its barcode depending
on what your scanner provides.
</Paragraph>

Expand All @@ -45,34 +45,33 @@ export default function EditBarcodeModal({ visible, currentBarcode, onSave, onCl
</Subheading>
) : null}

<TextInput
blurOnSubmit
ref={inputRef}
autoCompleteType="off"
<ScannerInput
isEnabled={visible}
label="Barcode (UPC)"
mode="outlined"
placeholder="Scan or type the product's barcode"
value={barcode}
autoSubmitTimeout={0}
style={styles.bottomSeparator}
returnKeyType="done"
onChangeText={setBarcode}
onSubmitEditing={() => {
Keyboard.dismiss();
onSave(barcode);
}}
onChange={setBarcode}
onSubmit={handleSave}
/>

<View style={styles.actionButtons}>
<Button onPress={onClose}>Cancel</Button>
<View style={styles.modalButtonRow}>
<Button
icon="close-circle"
variant="secondary"
mode="contained"
title="Cancel"
style={[styles.modalButton, styles.modalButtonSpacing]}
onPress={onClose}
/>
<Button
style={styles.topSeparator}
icon="arrow-right-circle"
mode="contained"
onPress={() => {
Keyboard.dismiss();
onSave(barcode);
}}
>
Save
</Button>
title="Save"
style={styles.modalButton}
onPress={handleSave}
/>
</View>
</View>
</View>
Expand Down
58 changes: 49 additions & 9 deletions src/screens/ProductDetails/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
import React from 'react';
import { Alert, ScrollView, TouchableOpacity, View } from 'react-native';
import { Caption, Card, Chip, Divider, Button as PaperButton, Paragraph, Subheading, Title } from 'react-native-paper';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import { connect } from 'react-redux';

import Button from '../../components/Button';
import { ProductDetailsSkeleton, CardSkeleton } from '../../components/ContentSkeleton';
import { CardSkeleton, ProductDetailsSkeleton } from '../../components/ContentSkeleton';
import EmptyView from '../../components/EmptyView';
import PrintModal from '../../components/PrintModal';
import { HYPHEN } from '../../constants';
import { resetToRoutes } from '../../NavigationService';
import { getProductByIdAction, updateProductIdentifierAction } from '../../redux/actions/products';
import { RootState } from '../../redux/reducers';
import Theme from '../../utils/Theme';
import EditBarcodeModal from './EditBarcodeModal';
import styles from './styles';
import { DispatchProps, Props, State } from './Types';
import { vmMapper } from './VMMapper';

function BackToSortationBanner() {
const handlePress = () => {
resetToRoutes([{ name: 'Drawer', params: { screen: 'Dashboard' } }, { name: 'Sortation' }]);
};

return (
<TouchableOpacity style={styles.backToSortationBanner} onPress={handlePress}>
<MaterialCommunityIcons name="arrow-bottom-left" size={20} color={Theme.colors.primary} />
<Paragraph style={styles.backToSortationText}>BACK TO SORTATION</Paragraph>
</TouchableOpacity>
);
}

function SectionCard({ title, children }: { title: string; children: React.ReactNode }) {
return (
<Card style={styles.sectionCard}>
Expand Down Expand Up @@ -94,16 +110,20 @@ class ProductDetails extends React.Component<Props, State> {
});
};

handleSaveBarcode = (newBarcode: string) => {
handleSaveBarcode = (newBarcode: string, clear: () => void) => {
const { productDetails } = this.state;
const id = productDetails.id;
if (!id) {
return;
}

this.props.updateProductIdentifierAction(id, 'upc', newBarcode, (response: any) => {
if (!this.state.editBarcodeVisible) {
return;
}
if (response?.error) {
Alert.alert('Error', response.errorMessage ?? 'Product identifier update failed.');
clear();
return;
}
this.setState({ editBarcodeVisible: false });
Expand Down Expand Up @@ -157,10 +177,16 @@ class ProductDetails extends React.Component<Props, State> {
render() {
const { isLoading, productDetails, visible, editBarcodeVisible } = this.state;
const product = this.props.selectedProduct;
const fromSortation = !!this.props.route?.params?.fromSortation;

if (isLoading) {
return (
<View style={styles.screenContainer}>
{fromSortation && (
<View style={styles.header}>
<BackToSortationBanner />
</View>
)}
<ProductDetailsSkeleton />
<View style={styles.detailsSection}>
<CardSkeleton />
Expand All @@ -172,13 +198,20 @@ class ProductDetails extends React.Component<Props, State> {

if (!productDetails?.id) {
return (
<View style={styles.emptyContainer}>
<EmptyView
isRefresh
title="Product Not Found"
description="Could not load product details."
onPress={this.getProduct}
/>
<View style={styles.screenContainer}>
{fromSortation && (
<View style={styles.header}>
<BackToSortationBanner />
</View>
)}
<View style={styles.emptyContainer}>
<EmptyView
isRefresh
title="Product Not Found"
description="Could not load product details."
onPress={this.getProduct}
/>
</View>
</View>
);
}
Expand All @@ -205,6 +238,13 @@ class ProductDetails extends React.Component<Props, State> {

<ScrollView>
<View style={styles.header}>
{fromSortation && (
<>
<BackToSortationBanner />
<Divider style={styles.contentDivider} />
</>
)}

<View style={styles.headerRow}>
<Chip icon="barcode" style={styles.chipDefault} textStyle={styles.chipText}>
{vm.productCode || HYPHEN}
Expand Down
21 changes: 21 additions & 0 deletions src/screens/ProductDetails/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,5 +102,26 @@ export default StyleSheet.create({
backgroundColor: 'white',
padding: Theme.spacing.large,
borderRadius: Theme.roundness * 2
},
backToSortationBanner: {
flexDirection: 'row',
alignItems: 'center'
},
modalButtonRow: {
flexDirection: 'row',
marginTop: Theme.spacing.large
},
modalButton: {
flex: 1
},
modalButtonSpacing: {
marginRight: Theme.spacing.small
},
backToSortationText: {
marginLeft: Theme.spacing.small,
fontSize: 14,
fontWeight: 'bold',
color: Theme.colors.primary,
letterSpacing: 0.5
}
});
20 changes: 11 additions & 9 deletions src/screens/Products/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ class Products extends React.Component<Props, State> {
this.props.getProductsAction(actionCallback, true);
};

goToProductDetails = (product: any) => {
this.props.navigation.navigate('ProductDetails', {
product,
fromSortation: this.props.route?.params?.fromSortation
});
};

onSearchByProductNamePress = () => {
this.setState({
searchBoxVisible: true
Expand Down Expand Up @@ -142,7 +149,7 @@ class Products extends React.Component<Props, State> {
error: emptyStateMessage('products', query, 'No products available')
});
} else if (data.length === 1) {
this.props.navigation.navigate('ProductDetails', { product: data[0] });
this.goToProductDetails(data[0]);
} else {
this.setState({
searchByName: { query, results: data },
Expand Down Expand Up @@ -190,7 +197,7 @@ class Products extends React.Component<Props, State> {
error: emptyStateMessage('products', query, 'No products available')
});
} else if (data.length === 1) {
this.props.navigation.navigate('ProductDetails', { product: data[0] });
this.goToProductDetails(data[0]);
} else {
this.setState({
searchByProductCode: { query, results: data },
Expand Down Expand Up @@ -297,7 +304,7 @@ class Products extends React.Component<Props, State> {
error: emptyStateMessage('products', query, 'No products available')
});
} else if (productList.length === 1) {
this.props.navigation.navigate('ProductDetails', { product: productList[0] });
this.goToProductDetails(productList[0]);
} else {
this.setState({
searchByProductCode: { query, results: data },
Expand Down Expand Up @@ -344,12 +351,7 @@ class Products extends React.Component<Props, State> {
<ListLoadingSkeleton visible count={5} CardComponent={ProductCardSkeleton} />
) : (
<>
<ProductsList
products={vm.list}
onProductTapped={(product) => {
this.props.navigation.navigate('ProductDetails', { product });
}}
/>
<ProductsList products={vm.list} onProductTapped={this.goToProductDetails} />
{vm?.list?.length === 0 && <CentralMessage message={vm.centralErrorMessage} />}
</>
)}
Expand Down
1 change: 1 addition & 0 deletions src/screens/Products/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ProductCategory } from '../../data/product/category/ProductCategory';
export interface OwnProps {
exit: () => void;
navigation: any;
route: any;
}

export interface StateProps {
Expand Down
Loading