diff --git a/apps/storefront/src/components/B3ProductList.tsx b/apps/storefront/src/components/B3ProductList.tsx index f3b7712f..eba89872 100644 --- a/apps/storefront/src/components/B3ProductList.tsx +++ b/apps/storefront/src/components/B3ProductList.tsx @@ -2,12 +2,16 @@ import { ReactElement, ChangeEvent, KeyboardEvent, + useState, + useEffect, } from 'react' import { Box, Typography, TextField, + Checkbox, + FormControlLabel, } from '@mui/material' import styled from '@emotion/styled' @@ -36,9 +40,6 @@ interface FlexProps { interface FlexItemProps { width?: string, padding?: string, - sx?: { - [k: string]: string - } } const Flex = styled('div')(({ @@ -75,7 +76,6 @@ const Flex = styled('div')(({ const FlexItem = styled('div')(({ width, padding = '0', - sx = {}, }: FlexItemProps) => ({ display: 'flex', flexGrow: width ? 0 : 1, @@ -83,7 +83,6 @@ const FlexItem = styled('div')(({ alignItems: 'center', width, padding, - ...sx, })) const ProductHead = styled('div')(() => ({ @@ -131,7 +130,10 @@ interface ProductProps { actionWidth?: string, quantityKey?: string, quantityEditable?: boolean, - onProductQuantityChange?: (id: number, newQuantity: number) => void + onProductQuantityChange?: (id: number, newQuantity: number) => void, + showCheckbox?: boolean, + setCheckedArr?: (items: Array) => void + selectAllText?: string, } export const B3ProductList: (props: ProductProps) => ReactElement = (props) => { @@ -143,8 +145,13 @@ export const B3ProductList: (props: ProductProps) => ReactElement = (props actionWidth = '100px', quantityEditable = false, onProductQuantityChange = noop, + showCheckbox = false, + setCheckedArr = noop, + selectAllText = 'Select all products', } = props + const [list, setList] = useState([]) + const [isMobile] = useMobile() const getProductPrice = (price: string | number) => { @@ -153,7 +160,7 @@ export const B3ProductList: (props: ProductProps) => ReactElement = (props return priceNumber.toFixed(2) } - const getQuantity = (product: any) => parseInt(product[quantityKey].toString(), 10) || '' + const getQuantity = (product: any) => parseInt(product[quantityKey]?.toString() || '', 10) || '' const getProductTotals = (quantity: number, price: string | number) => { const priceNumber = parseFloat(price.toString()) || 0 @@ -179,6 +186,36 @@ export const B3ProductList: (props: ProductProps) => ReactElement = (props } } + const handleSelectAllChange = () => { + const newList = [...list] + if (newList.length === products.length) { + setList([]) + } else { + setList([...products]) + } + } + + const handleSelectChange = (product: ProductItem) => { + const newList = [...list] + const index = newList.findIndex((item) => item.id === product.id) + if (index !== -1) { + newList.splice(index, 1) + } else { + newList.push(product) + } + setList(newList) + } + + const isChecked = (product: ProductItem) => list.findIndex((item) => item.id === product.id) !== -1 + + useEffect(() => { + setCheckedArr(list) + }, [list]) + + useEffect(() => { + setList([]) + }, [products]) + const itemStyle = isMobile ? mobileItemStyle : defaultItemStyle return products.length > 0 ? ( @@ -189,6 +226,14 @@ export const B3ProductList: (props: ProductProps) => ReactElement = (props isHeader isMobile={isMobile} > + { + showCheckbox && ( + + ) + } Product @@ -213,12 +258,37 @@ export const B3ProductList: (props: ProductProps) => ReactElement = (props ) } + { + isMobile && showCheckbox && ( + + )} + sx={{ + paddingLeft: '0.6rem', + }} + /> + ) + } + { products.map((product) => ( + { + showCheckbox && ( + handleSelectChange(product)} + /> + ) + } (props: ProductProps) => ReactElement = (props {isMobile && Price:} {`${currency} ${getProductPrice(product.base_price)}`} @@ -283,10 +351,8 @@ export const B3ProductList: (props: ProductProps) => ReactElement = (props {isMobile && Total:} {`${currency} ${getProductTotals(getQuantity(product) || 0, product.base_price)}`} diff --git a/apps/storefront/src/pages/orderDetail/components/OrderAction.tsx b/apps/storefront/src/pages/orderDetail/components/OrderAction.tsx index 640964bf..38ec5271 100644 --- a/apps/storefront/src/pages/orderDetail/components/OrderAction.tsx +++ b/apps/storefront/src/pages/orderDetail/components/OrderAction.tsx @@ -157,8 +157,8 @@ const OrderCard = (props: OrderCardProps) => { confirmText: 'Submit return request', }, { - dialogTitle: 'Add to shipping list', - type: 'shippingList', + dialogTitle: 'Add to shopping list', + type: 'shoppingList', description: 'Select products and quantity to add to shopping list', confirmText: 'Add to shopping list', }, @@ -184,8 +184,6 @@ const OrderCard = (props: OrderCardProps) => { navigate('/invoiceDetail/1') } else if (name === 'printInvoice') { window.open(`/account.php?action=print_invoice&order_id=${orderId}`) - } else if (name === 'return') { - // TODO } else { setOpen(true) setType(name) @@ -323,6 +321,7 @@ export const OrderAction = (props: OrderActionProps) => { products, orderId, ipStatus = 0, + canReturn = false, } = detailsData if (!orderId) { @@ -397,12 +396,12 @@ export const OrderAction = (props: OrderActionProps) => { key: 'Return', name: 'return', variant: 'outlined', - isCanShow: true, + isCanShow: canReturn, }, { value: 'ADD TO SHOPPING LIST', - key: 'add-to-shipping-list', - name: 'shippingList', + key: 'add-to-shopping-list', + name: 'shoppingList', variant: 'outlined', isCanShow: isB2BUser, }, diff --git a/apps/storefront/src/pages/orderDetail/components/OrderCheckboxProduct.tsx b/apps/storefront/src/pages/orderDetail/components/OrderCheckboxProduct.tsx index 86d17f74..c133afe3 100644 --- a/apps/storefront/src/pages/orderDetail/components/OrderCheckboxProduct.tsx +++ b/apps/storefront/src/pages/orderDetail/components/OrderCheckboxProduct.tsx @@ -42,6 +42,7 @@ interface FlexItemProps { width?: string, padding?: string, flexBasis?: string, + minHeight?: string, } const Flex = styled('div')(({ @@ -287,14 +288,18 @@ export const OrderCheckboxProduct = (props: OrderCheckboxProductProps) => { ))} - + {isMobile && Price: } {`${currencyInfo.currency_token} ${getProductPrice(product.base_price)}`} { }} /> - + {isMobile && Cost: } {`${currencyInfo.currency_token} ${getProductTotals(getProductQuantity(product), product.base_price)}`} diff --git a/apps/storefront/src/pages/orderDetail/components/OrderDialog.tsx b/apps/storefront/src/pages/orderDetail/components/OrderDialog.tsx index 61d23af1..eec90d87 100644 --- a/apps/storefront/src/pages/orderDetail/components/OrderDialog.tsx +++ b/apps/storefront/src/pages/orderDetail/components/OrderDialog.tsx @@ -17,6 +17,10 @@ import { Divider, } from '@mui/material' +import { + useForm, +} from 'react-hook-form' + import { useMobile, } from '@/hooks' @@ -27,8 +31,13 @@ import { import { addProductToShoppingList, + getB2BVariantInfoBySkus, } from '@/shared/service/b2b' +import { + B3CustomForm, +} from '@/components' + import { snackbar, } from '@/utils' @@ -42,6 +51,10 @@ import { } from './OrderShoppingList' import CreateShoppingList from './CreateShoppingList' +import { + getReturnFormFields, +} from '../shared/config' + import { EditableProductItem, OrderProductItem, @@ -69,7 +82,7 @@ export const OrderDialog: (props: OrderDialogProps) => ReactElement = ({ open, products = [], type, - currentDialogData = {}, + currentDialogData = null, setOpen, itemKey, currencyInfo, @@ -84,15 +97,128 @@ export const OrderDialog: (props: OrderDialogProps) => ReactElement = ({ const [isMobile] = useMobile() + const [returnFormFields] = useState(getReturnFormFields()) + + const { + control, + handleSubmit, + getValues, + formState: { + errors, + }, + setValue, + } = useForm({ + mode: 'all', + }) + const handleClose = () => { setOpen(false) } + const handleReturn = () => { + handleSubmit((data) => { + console.log(11111, data) + })() + } + + const validateProductNumber = (variantInfoList: CustomFieldItems, skus: string[]) => { + const notStockSku: string[] = [] + const notPurchaseSku: string[] = [] + + skus.forEach((sku) => { + const variantInfo : CustomFieldItems | null = (variantInfoList || []).find((variant: CustomFieldItems) => variant.variantSku.toUpperCase() === sku.toUpperCase()) + + if (!variantInfo) { + return + } + + const { + purchasingDisabled = '1', + maxQuantity, + minQuantity, + stock, + } = variantInfo + + const quantity = editableProducts.find((product) => product.sku === sku)?.editQuantity || 1 + + if (purchasingDisabled === '1') { + notPurchaseSku.push(sku) + return + } + + if (quantity > stock || (minQuantity !== 0 && stock < minQuantity) || (maxQuantity !== 0 && quantity > maxQuantity)) { + notStockSku.push(sku) + } + }) + + if (notStockSku.length > 0) { + snackbar.error(`SKU ${notPurchaseSku} not enough inventory`) + return false + } + + if (notPurchaseSku.length > 0) { + snackbar.error(`SKU ${notPurchaseSku} no longer for sale`) + return false + } + + return true + } + + const handleReorder = async () => { + setIsRequestLoading(true) + + try { + const items: CustomFieldItems[] = [] + const skus: string[] = [] + editableProducts.forEach((product) => { + if (checkedArr.includes(product.variant_id)) { + items.push({ + quantity: parseInt(`${product.editQuantity}`, 10) || 1, + productId: product.product_id, + variantId: product.variant_id, + optionSelections: (product.optionList || []).map((option) => ({ + optionId: option.optionId, + optionValue: option.optionValue, + })), + }) + + skus.push(product.sku) + } + }) + + if (skus.length <= 0) { + return + } + + const { + variantSku: variantInfoList, + }: CustomFieldItems = await getB2BVariantInfoBySkus({ + skus, + }) + + if (!validateProductNumber(variantInfoList, skus)) { + return + } + + console.log(33333, items, skus) + } finally { + setIsRequestLoading(false) + } + } + const handleSaveClick = () => { - if (type === 'shippingList') { + if (type === 'shoppingList') { handleClose() setOpenShoppingList(true) } + + if (type === 'reOrder') { + handleReorder() + } + + if (type === 'return') { + handleReturn() + } } const handleCreateShoppingClick = () => { @@ -207,7 +333,7 @@ export const OrderDialog: (props: OrderDialogProps) => ReactElement = ({ borderBottom: '1px solid #D9DCE9', }} > - {currentDialogData.dialogTitle} + {currentDialogData?.dialogTitle || ''} ReactElement = ({ margin: '1rem 0', }} > - {currentDialogData.description} + {currentDialogData?.description || ''} ReactElement = ({ currencyInfo={currencyInfo} setCheckedArr={setCheckedArr} /> + + { + type === 'return' && ( + <> + + Additional Information + + + + ) + } @@ -233,7 +381,7 @@ export const OrderDialog: (props: OrderDialogProps) => ReactElement = ({ onClick={handleSaveClick} autoFocus > - {currentDialogData.confirmText} + {currentDialogData?.confirmText || 'Save'} diff --git a/apps/storefront/src/pages/orderDetail/components/OrderShipping.tsx b/apps/storefront/src/pages/orderDetail/components/OrderShipping.tsx index 243c6b03..4d613392 100644 --- a/apps/storefront/src/pages/orderDetail/components/OrderShipping.tsx +++ b/apps/storefront/src/pages/orderDetail/components/OrderShipping.tsx @@ -18,16 +18,16 @@ import { format, } from 'date-fns' +import { + B3ProductList, +} from '@/components' + import { OrderProductItem, OrderShippedItem, OrderShippingsItem, } from '../../../types' -import { - OrderProduct, -} from './OrderProduct' - import { OrderDetailsContext, } from '../context/OrderDetailsContext' @@ -84,8 +84,6 @@ export const OrderShipping = () => { return `shipped on ${time}, by ${shippingProvider}, ${shippingMethod}` } - const getShippingProductQuantity = (item: OrderProductItem) => item.current_quantity_shipped || 0 - const getNotShippingProductQuantity = (item: OrderProductItem) => { const notShipNumber = item.quantity - item.quantity_shipped @@ -163,8 +161,8 @@ export const OrderShipping = () => { ) } - @@ -186,8 +184,8 @@ export const OrderShipping = () => { - diff --git a/apps/storefront/src/pages/orderDetail/context/OrderDetailsContext.tsx b/apps/storefront/src/pages/orderDetail/context/OrderDetailsContext.tsx index 4fe6dd83..1915b751 100644 --- a/apps/storefront/src/pages/orderDetail/context/OrderDetailsContext.tsx +++ b/apps/storefront/src/pages/orderDetail/context/OrderDetailsContext.tsx @@ -35,6 +35,7 @@ export interface OrderDetailsState { ipStatus?: number, invoiceId?: number, addressLabelPermission?: boolean, + canReturn?: boolean, } interface OrderDetailsAction { type: string, @@ -72,6 +73,7 @@ const initState = { ipStatus: 0, invoiceId: 0, addressLabelPermission: false, + canReturn: false, } export const OrderDetailsContext = createContext({ diff --git a/apps/storefront/src/pages/orderDetail/shared/B2BOrderData.ts b/apps/storefront/src/pages/orderDetail/shared/B2BOrderData.ts index a8653fbd..4dcfb8e8 100644 --- a/apps/storefront/src/pages/orderDetail/shared/B2BOrderData.ts +++ b/apps/storefront/src/pages/orderDetail/shared/B2BOrderData.ts @@ -29,6 +29,7 @@ const getOrderShipping = (data: B2BOrderData) => { itemsInfo.push({ ...product, current_quantity_shipped: item.quantity, + not_shipping_number: product.quantity - product.quantity_shipped, }) } }) @@ -45,7 +46,10 @@ const getOrderShipping = (data: B2BOrderData) => { ...(shippedItems.filter((shippedItem: OrderShippedItem) => shippedItem.order_address_id === address.id)), ], notShip: { - itemsInfo: products.filter((product: OrderProductItem) => product.quantity > product.quantity_shipped && address.id === product.order_address_id), + itemsInfo: products.filter((product: OrderProductItem) => { + product.not_shipping_number = product.quantity - product.quantity_shipped + return product.quantity > product.quantity_shipped && address.id === product.order_address_id + }), }, })) @@ -148,4 +152,5 @@ export const convertB2BOrderDetails = (data: B2BOrderData) => ({ customStatus: data.customStatus, ipStatus: +data.ipStatus || 0, // 0: no invoice, 1,2: have invoice invoiceId: +(data.invoiceId || 0), + canReturn: data.canReturn, }) diff --git a/apps/storefront/src/pages/orderDetail/shared/config.ts b/apps/storefront/src/pages/orderDetail/shared/config.ts new file mode 100644 index 00000000..2334ba87 --- /dev/null +++ b/apps/storefront/src/pages/orderDetail/shared/config.ts @@ -0,0 +1,56 @@ +export const getReturnFormFields = () => [ + { + name: 'return_reason', + label: 'Return reason', + required: true, + xs: 12, + variant: 'filled', + size: 'small', + fieldType: 'dropdown', + default: '', + options: [ + { + label: 'Received Wrong Product', value: 'Received Wrong Product', + }, + { + label: 'Wrong Product Ordered', value: 'Wrong Product Ordered', + }, + { + label: 'Not Satisfied With The Product', value: 'Not Satisfied With The Product', + }, + { + label: 'There Was A Problem With The Product', value: 'There Was A Problem With The Product', + }, + ], + }, { + name: 'return_action', + label: 'Return action', + required: true, + xs: 12, + variant: 'filled', + size: 'small', + fieldType: 'dropdown', + default: '', + options: [ + { + label: 'Repair', value: 'Repair', + }, + { + label: 'Replacement', value: 'Replacement', + }, + { + label: 'Store Credit', value: 'Store Credit', + }, + ], + }, { + name: 'return_comments', + label: 'Comment', + required: true, + xs: 12, + rows: 5, + variant: 'filled', + size: 'small', + fieldType: 'multiline', + default: '', + }, +] diff --git a/apps/storefront/src/types/orderDetail.ts b/apps/storefront/src/types/orderDetail.ts index f17b7184..cabf0f64 100644 --- a/apps/storefront/src/types/orderDetail.ts +++ b/apps/storefront/src/types/orderDetail.ts @@ -65,6 +65,7 @@ export interface OrderProductItem { wrapping_message: string, wrapping_name: string, current_quantity_shipped?: number, + not_shipping_number?: number, } export interface EditableProductItem extends OrderProductItem{