META-INF.resources.js.multishipping.Multishipping.tsx Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com.liferay.commerce.order.content.web
Show all versions of com.liferay.commerce.order.content.web
Liferay Commerce Order Content Web
/**
* SPDX-FileCopyrightText: (c) 2024 Liferay, Inc. https://liferay.com
* SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
*/
import ClayEmptyState from '@clayui/empty-state';
import {ClayCheckbox, ClayInput} from '@clayui/form';
import ClayLoadingIndicator from '@clayui/loading-indicator';
import ClayManagementToolbar, {
ClayResultsBar,
} from '@clayui/management-toolbar';
import {ClayPaginationBarWithBasicItems} from '@clayui/pagination-bar';
import ClayTable from '@clayui/table';
// @ts-ignore
import {CommerceServiceProvider, commerceEvents} from 'commerce-frontend-js';
import React, {useCallback, useEffect, useState} from 'react';
import './Multishipping.scss';
import ClayButton from '@clayui/button';
import ClayIcon from '@clayui/icon';
import classNames from 'classnames';
import {openConfirmModal} from 'frontend-js-web';
import AddDeliveryGroupButton from './AddDeliveryGroupButton';
import DeliveryGroupHeaderCell from './DeliveryGroupHeaderCell';
import {showError} from './ErrorMessage';
import OrderItemRow, {
copyColumnOrderItem,
removeOrderItem,
resetOrderItem,
splitOrderItem,
} from './OrderItemRow';
import {
IAPIResponseError,
IDeliveryGroup,
IOrderItem,
IOrderItemAPIResponse,
IOrderItemDeliveryGroup,
} from './Types';
const MAX_DELIVERY_GROUPS = 20;
interface IMultishippingProps {
accountId: number;
defaultAddressId?: number;
defaultAddressName?: string;
namespace?: string;
orderId: number;
readonly?: boolean;
spritemap?: string;
}
export function formatCartItem(
deliveryGroup: IDeliveryGroup,
orderItem: IOrderItem,
quantity: number = 1
) {
return {
deliveryGroup: deliveryGroup.name,
options: orderItem.options,
quantity: Number(
Number(quantity).toFixed(orderItem.skuUnitOfMeasure?.precision || 0)
),
replacedSkuId: orderItem.replacedSkuId || 0,
requestedDeliveryDate: deliveryGroup.deliveryDate,
shippingAddressId: deliveryGroup.addressId,
skuId: orderItem.skuId,
skuUnitOfMeasure: orderItem.skuUnitOfMeasure,
};
}
const createRequestData = (
deliveryGroups: Array,
items: Array
) => {
const data: Array = [];
items.forEach((orderItem) => {
if (deliveryGroups.length) {
for (const [deliveryGroupId, orderItemConf] of Object.entries(
orderItem.deliveryGroups || {}
)) {
let deliveryGroup = null;
if (0 === Number(deliveryGroupId)) {
deliveryGroup = deliveryGroups[0];
}
else {
deliveryGroup = deliveryGroups.find((item) => {
return item.id === Number(deliveryGroupId);
});
}
if (deliveryGroup && orderItemConf.quantity > 0) {
data.push({
deliveryGroup: deliveryGroup.name,
id: orderItemConf.orderItemId,
options: orderItemConf.options,
quantity: orderItemConf.quantity,
replacedSkuId: orderItemConf.replacedSkuId,
requestedDeliveryDate: deliveryGroup.deliveryDate,
shippingAddressId: deliveryGroup.addressId,
skuId: orderItemConf.skuId,
skuUnitOfMeasure: orderItemConf.skuUnitOfMeasure,
});
}
}
}
else {
if (orderItem.quantity > 0) {
data.push(
formatCartItem(
{
addressId: 0,
deliveryDate: '',
id: 0,
name: '',
},
orderItem,
orderItem.quantity
) as IOrderItem
);
}
}
});
return data;
};
const Multishipping = ({
accountId,
defaultAddressId = 0,
defaultAddressName = '',
namespace = '',
orderId,
readonly = false,
spritemap,
}: IMultishippingProps) => {
const [deliveryGroups, setDeliveryGroups] = useState>(
[]
);
const [checkedOrderItemIds, setCheckedOrderItemIds] = useState<
Array
>([]);
const [filterFormattedOrderItems, setFilteredFormattedOrderItems] =
useState>([]);
const [formattedOrderItems, setFormattedOrderItems] = useState<
Array
>([]);
const [loading, setLoading] = useState(true);
const [pagination, setPagination] = useState({
currentPage: 1,
pageSize: 20,
});
const [saving, setSaving] = useState(false);
const [search, setSearch] = useState('');
const prepareData = useCallback(async (items: Array) => {
const formattedDeliveryGroups: Array = [];
const formattedItems: Array = [];
items.forEach((orderItem) => {
let deliveryGroup = formattedDeliveryGroups.find((item) => {
return (
item.name === (orderItem.deliveryGroup || '') &&
item.deliveryDate ===
(orderItem.requestedDeliveryDate || '') &&
item.addressId === orderItem.shippingAddressId
);
});
if (!deliveryGroup && orderItem.shippingAddressId > 0) {
deliveryGroup = {
addressId: orderItem.shippingAddressId,
deliveryDate: orderItem.requestedDeliveryDate || '',
id: Math.floor(Math.random() * 100000000),
name: orderItem.deliveryGroup || '',
};
formattedDeliveryGroups.push(deliveryGroup);
}
let formattedItem = formattedItems.find((item) => {
return (
item.skuId === orderItem.skuId &&
item.skuUnitOfMeasure?.key ===
orderItem.skuUnitOfMeasure?.key &&
item.options === orderItem.options &&
orderItem.deliveryGroup !== ''
);
});
if (
!formattedItem ||
(formattedItem &&
deliveryGroup &&
(formattedItem.deliveryGroups || {})[deliveryGroup.id])
) {
formattedItem = {
...orderItem,
deliveryGroups: {},
quantity: 0,
};
formattedItems.push(formattedItem);
}
if (deliveryGroup) {
formattedItem.deliveryGroups = {
...formattedItem.deliveryGroups,
[deliveryGroup.id]: {
options: orderItem.options,
orderItemId: orderItem.id,
originalQuantity: orderItem.quantity,
quantity: orderItem.quantity,
replacedSkuId: orderItem.replacedSkuId || 0,
skuId: orderItem.skuId,
skuUnitOfMeasure: orderItem.skuUnitOfMeasure,
} as IOrderItemDeliveryGroup,
};
}
else {
formattedItem.deliveryGroups = {
[0]: {
options: orderItem.options,
orderItemId: orderItem.id,
originalQuantity: orderItem.quantity,
quantity: orderItem.quantity,
replacedSkuId: orderItem.replacedSkuId || 0,
skuId: orderItem.skuId,
skuUnitOfMeasure: orderItem.skuUnitOfMeasure,
},
};
}
formattedItem.quantity =
formattedItem.quantity + orderItem.quantity;
});
if (formattedDeliveryGroups.length) {
formattedDeliveryGroups.sort((item1, item2) => {
return item1.name.localeCompare(item2.name);
});
if (formattedItems.find((item) => item.deliveryGroup === '')) {
try {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
await updateFullCart(
createRequestData(
formattedDeliveryGroups,
formattedItems
)
);
return;
}
catch (error) {
showError(error as IAPIResponseError);
}
}
}
else if (defaultAddressId) {
try {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
await updateFullCart(
formattedItems.map((item) => {
return formatCartItem(
{
addressId: defaultAddressId,
deliveryDate: '',
id: 0,
name: defaultAddressName || 'Default',
},
item,
item.quantity
);
})
);
return;
}
catch (error) {
showError(error as IAPIResponseError);
}
}
setDeliveryGroups(formattedDeliveryGroups);
setFormattedOrderItems(
formattedItems.sort(
(item1, item2) =>
item1.sku?.localeCompare(item2.sku || '') ||
item1.id - item2.id
)
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const loadOrderItemData = useCallback(
({updatedFromCart = false}: {updatedFromCart: boolean}) => {
if (!updatedFromCart) {
return;
}
setLoading(true);
CommerceServiceProvider.DeliveryCartAPI('v1')
.getItemsByCartId(orderId, {pageSize: -1})
.then(async (response: IOrderItemAPIResponse) => {
await prepareData(response.items);
})
.catch((error: IAPIResponseError) => {
showError(error);
})
.finally(() => {
setLoading(false);
});
},
[orderId, prepareData]
);
const updateFullCart = useCallback(
async (data) => {
setSaving(true);
await CommerceServiceProvider.DeliveryCartAPI('v1')
.updateCartById(
orderId,
{
cartItems: data,
},
{pageSize: -1}
)
.then(async (response: IOrderItemAPIResponse) => {
await prepareData(response.cartItems);
Liferay.fire(commerceEvents.CURRENT_ORDER_UPDATED, {
order: response,
updatedFromCart: false,
});
Liferay.fire(commerceEvents.ORDER_INFORMATION_ALTERED, {
order: response,
});
})
.finally(() => {
setSaving(false);
});
},
[orderId, prepareData]
);
const handleBulkAction = useCallback(
async (action: string) => {
const originalFormattedOrderItems = JSON.parse(
JSON.stringify(formattedOrderItems)
);
try {
let currentFormattedOrderItems = JSON.parse(
JSON.stringify(formattedOrderItems)
);
for (const orderItemId of checkedOrderItemIds) {
const orderItem = currentFormattedOrderItems.find(
(orderItem: IOrderItem) => orderItem.id === orderItemId
);
if (!orderItem) {
continue;
}
let currentOrderItem: IOrderItem | null = null;
if (action === 'remove') {
currentOrderItem = removeOrderItem(orderItem);
}
else if (action === 'reset') {
currentOrderItem = resetOrderItem(
deliveryGroups[0],
orderItem
);
}
else if (action === 'split') {
currentOrderItem = splitOrderItem(
deliveryGroups,
orderItem
);
}
else {
currentOrderItem = copyColumnOrderItem(
deliveryGroups,
orderItem
);
}
currentFormattedOrderItems = currentFormattedOrderItems.map(
(item: IOrderItem) => {
if (item.id === orderItemId) {
return currentOrderItem;
}
return item;
}
);
}
await updateFullCart(
createRequestData(
deliveryGroups,
currentFormattedOrderItems
)
);
setCheckedOrderItemIds([]);
}
catch (error) {
setFormattedOrderItems(originalFormattedOrderItems);
showError({
detail: Liferay.Language.get(
'the-item-s-quantity-is-not-valid-for-the-number-of-delivery-groups'
),
});
}
},
[
checkedOrderItemIds,
deliveryGroups,
formattedOrderItems,
updateFullCart,
]
);
const handleDeleteDeliveryGroup = useCallback(
async (deliveryGroup) => {
try {
if (deliveryGroups.length === 1) {
await updateFullCart(
formattedOrderItems.map((item) => {
return formatCartItem(
{
addressId: 0,
deliveryDate: '',
id: 0,
name: '',
},
item,
item.quantity
);
})
);
}
else {
const updatedDeliveryGroups = deliveryGroups.filter(
(item) => {
return item.id !== deliveryGroup.id;
}
);
await updateFullCart(
createRequestData(
updatedDeliveryGroups,
formattedOrderItems
)
);
}
}
catch (error) {
showError(error as IAPIResponseError);
}
},
[deliveryGroups, formattedOrderItems, updateFullCart]
);
const handlePaginationDeltaChange = useCallback((value) => {
setPagination((prevState) => ({
...prevState,
pageSize: value,
}));
}, []);
const handlePaginationPageChange = useCallback((value) => {
setPagination((prevState) => ({
...prevState,
currentPage: value,
}));
}, []);
const handleRowSelection = useCallback(async (orderItemId: number) => {
setCheckedOrderItemIds((prevState) => {
const currentCheckedOrderItemIds = [...prevState];
if (currentCheckedOrderItemIds.includes(orderItemId)) {
currentCheckedOrderItemIds.splice(
currentCheckedOrderItemIds.indexOf(orderItemId),
1
);
}
else {
currentCheckedOrderItemIds.push(orderItemId);
}
return currentCheckedOrderItemIds;
});
}, []);
const handleRowUpdate = useCallback(
async (orderItem: IOrderItem, saveFullOrder: boolean = false) => {
const originalFormattedOrderItems = formattedOrderItems.map(
(item) => {
return {...item};
}
);
const currentFormattedOrderItems = formattedOrderItems.map(
(item) => {
if (item.id === orderItem.id) {
return orderItem;
}
return item;
}
);
setFormattedOrderItems(currentFormattedOrderItems);
if (saveFullOrder) {
try {
await updateFullCart(
createRequestData(
deliveryGroups,
currentFormattedOrderItems
)
);
}
catch (error) {
setFormattedOrderItems(originalFormattedOrderItems);
showError(error as IAPIResponseError);
}
}
},
[deliveryGroups, formattedOrderItems, updateFullCart]
);
const handleSubmitDeliveryGroup = useCallback(
async (deliveryGroup: IDeliveryGroup) => {
const deliveryGroupsState = [...deliveryGroups];
const index = deliveryGroupsState.findIndex(
(item: IDeliveryGroup) => item.id === deliveryGroup.id
);
if (index >= 0) {
deliveryGroupsState[index] = deliveryGroup;
try {
await updateFullCart(
createRequestData(
deliveryGroupsState,
formattedOrderItems
)
);
}
catch (error) {
showError(error as IAPIResponseError);
}
}
else {
deliveryGroup.id = new Date().getTime();
deliveryGroupsState.push(deliveryGroup);
setDeliveryGroups(deliveryGroupsState);
if (deliveryGroupsState.length === 1) {
try {
await updateFullCart(
formattedOrderItems.map((item) => {
return formatCartItem(
deliveryGroup,
item,
item.quantity
);
})
);
}
catch (error) {
showError(error as IAPIResponseError);
setDeliveryGroups([]);
return;
}
}
Liferay.Util.openToast({
message: Liferay.Language.get(
'your-request-completed-successfully'
),
});
}
},
[deliveryGroups, formattedOrderItems, updateFullCart]
);
useEffect(() => {
loadOrderItemData({updatedFromCart: true});
Liferay.on(commerceEvents.CART_UPDATED, loadOrderItemData);
return () => {
Liferay.detach(
commerceEvents.CART_UPDATED,
loadOrderItemData as any
);
};
}, [loadOrderItemData]);
useEffect(() => {
if (search) {
setFilteredFormattedOrderItems(
formattedOrderItems.filter((item) => {
return (
(item.name || '')
.toLowerCase()
.indexOf(search.toLowerCase()) >= 0 ||
(item.sku || '')
.toLowerCase()
?.indexOf(search.toLowerCase()) >= 0
);
})
);
}
else {
setFilteredFormattedOrderItems(formattedOrderItems);
}
}, [formattedOrderItems, search]);
const managementBar = (
<>
{!checkedOrderItemIds.length && (
{!readonly && (
{
setCheckedOrderItemIds(
target.checked
? filterFormattedOrderItems.map(
(orderItem) =>
orderItem.id
)
: []
);
}}
/>
)}
{
setSearch(value);
}}
type="text"
value={search}
/>
{!readonly && (
=
MAX_DELIVERY_GROUPS
}
handleSubmit={handleSubmitDeliveryGroup}
hasManageAddressesPermission={true}
namespace={namespace}
spritemap={spritemap}
/>
)}
)}
{!!checkedOrderItemIds.length && (
{
setCheckedOrderItemIds(
target.checked
? filterFormattedOrderItems.map(
(orderItem) => orderItem.id
)
: []
);
}}
/>
{`${checkedOrderItemIds.length} ${Liferay.Language.get('of')} ${filterFormattedOrderItems.length}`}
{
setCheckedOrderItemIds(
filterFormattedOrderItems.map(
(orderItem) => orderItem.id
)
);
}}
>
{Liferay.Language.get('select-all')}
{
openConfirmModal({
message: Liferay.Language.get(
'if-the-total-quantity-cannot-be-equally-distributed,-any-remaining-units-will-be-allocated-to-the-primary-delivery-group'
),
onConfirm: async (isConfirmed) => {
if (isConfirmed) {
await handleBulkAction('split');
}
},
});
}}
>
{Liferay.Language.get('split-quantity-evenly')}
{
openConfirmModal({
message: Liferay.Language.get(
'by-resetting-the-rows,-all-columns-will-be-set-to-zero,-except-the-first-one'
),
onConfirm: async (isConfirmed) => {
if (isConfirmed) {
await handleBulkAction('reset');
}
},
});
}}
>
{Liferay.Language.get('reset-rows')}
{
await handleBulkAction('copy');
}}
>
{Liferay.Language.get('copy-column-1-to-all')}
{
openConfirmModal({
message: Liferay.Language.get(
'by-removing-the-items,-they-will-disappear-from-the-list-of-ordered-items'
),
onConfirm: async (isConfirmed) => {
if (isConfirmed) {
await handleBulkAction(
'remove'
);
}
},
});
}}
>
{Liferay.Language.get('remove-items')}
)}
>
);
const view = (
{formattedOrderItems.length ? (
{!readonly && (
)}
{Liferay.Language.get('sku')}
{Liferay.Language.get('quantity')}
{deliveryGroups.map((deliveryGroup) => (
))}
{filterFormattedOrderItems
.slice(
(pagination.currentPage - 1) *
pagination.pageSize,
pagination.currentPage * pagination.pageSize
)
.map((orderItem, currentIndex) => (
))}
) : (
)}
);
const paginationComponent = (
({
label: size,
}))}
ellipsisBuffer={3}
onActiveChange={handlePaginationPageChange}
onDeltaChange={handlePaginationDeltaChange}
totalItems={filterFormattedOrderItems.length}
/>
);
return (
{managementBar}
{loading ? (
) : (
<>
{view}
{paginationComponent}
>
)}
);
};
export default Multishipping;