All Downloads are FREE. Search and download functionalities are using the official Maven repository.

META-INF.resources.js.multishipping.Multishipping.tsx Maven / Gradle / Ivy

There is a newer version: 4.0.127
Show newest version
/**
 * 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;




© 2015 - 2024 Weber Informatics LLC | Privacy Policy