@evenicanpm/storefront-core 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__mocks__/get-product-by-id.ts +0 -1
- package/__mocks__/query-client.ts +1 -1
- package/package.json +3 -3
- package/src/api-manager/datasources/d365/d365-address.datasource.ts +2 -2
- package/src/api-manager/datasources/d365/d365-cart.datasource.ts +4 -5
- package/src/api-manager/datasources/d365/d365-categories.datasource.ts +1 -1
- package/src/api-manager/datasources/d365/d365-product.datasource.ts +21 -7
- package/src/api-manager/datasources/d365/d365-session.datasource.ts +5 -7
- package/src/api-manager/datasources/d365/d365-user.datasource.ts +12 -23
- package/src/api-manager/datasources/d365/d365.datasource.ts +0 -2
- package/src/api-manager/datasources/d365/index.ts +2 -4
- package/src/api-manager/datasources/d365/utils/decode-jwt.ts +1 -2
- package/src/api-manager/datasources/d365/utils/parse-object.ts +1 -1
- package/src/api-manager/datasources/e4/address/e4-address.remaps.ts +2 -2
- package/src/api-manager/datasources/e4/address/e4-address.translator.ts +1 -1
- package/src/api-manager/datasources/e4/cart/e4-cart.datasource.ts +28 -27
- package/src/api-manager/datasources/e4/cart/e4-cart.remaps.ts +1 -1
- package/src/api-manager/datasources/e4/cart/e4-cart.translator.ts +1 -1
- package/src/api-manager/datasources/e4/categories/e4-categories.translator.ts +20 -20
- package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +471 -7
- package/src/api-manager/datasources/e4/order/e4-order.remaps.ts +2 -2
- package/src/api-manager/datasources/e4/organization/e4-organization.datasource.ts +2 -8
- package/src/api-manager/datasources/e4/product/e4-product.remaps.ts +3 -3
- package/src/api-manager/datasources/e4/product/e4-product.translator.ts +13 -29
- package/src/api-manager/datasources/e4/session/e4-session.datasource.ts +1 -3
- package/src/api-manager/datasources/e4/user/e4-user.remaps.ts +2 -4
- package/src/api-manager/datasources/e4/utils/unwrap-e4-variants.ts +4 -4
- package/src/api-manager/index.ts +1 -1
- package/src/api-manager/lib/category-helpers.ts +3 -2
- package/src/api-manager/lib/get-graphql-client.ts +1 -2
- package/src/api-manager/services/cart/mutations/add-discount-code.ts +2 -2
- package/src/api-manager/services/cart/mutations/add-to-cart.ts +2 -2
- package/src/api-manager/services/product/server/product.server.ts +1 -1
- package/src/api-manager/types/Datasource.ts +4 -9
- package/src/auth/auth-options.test.ts +2 -58
- package/src/auth/auth-options.ts +1 -2
- package/src/auth/next-auth-cookie-manager.ts +0 -2
- package/src/auth/refresh-token.ts +3 -8
- package/src/auth/signout.ts +1 -1
- package/src/cms/blocks/block-manager.tsx +3 -3
- package/src/cms/blocks/components/category-card.tsx +1 -1
- package/src/cms/blocks/components/category-carousel/index.tsx +3 -1
- package/src/cms/blocks/components/footer/index.tsx +2 -2
- package/src/cms/blocks/components/footer/sections/footer-links.tsx +2 -2
- package/src/cms/blocks/components/footer/sections/footer-logo.tsx +1 -1
- package/src/cms/blocks/components/footer/sections/footer-social-links.tsx +10 -2
- package/src/cms/blocks/components/hero-carousel/index.tsx +29 -25
- package/src/cms/blocks/components/media-card.tsx +1 -1
- package/src/cms/blocks/components/product-carousel/index.tsx +1 -1
- package/src/cms/blocks/components/product-section-fullwidth/index.tsx +2 -2
- package/src/cms/blocks/components/seo/index.tsx +5 -1
- package/src/cms/blocks/components/services/index.tsx +1 -1
- package/src/cms/blocks/components/services/service-card.tsx +1 -1
- package/src/cms/blocks/components/shared/featured-product-card.tsx +4 -1
- package/src/cms/blocks/components/shared/product-category-item.tsx +1 -1
- package/src/cms/blocks/components/shared/product-grid.tsx +4 -1
- package/src/cms/blocks/components/shared/top-categories-card.tsx +5 -1
- package/src/cms/blocks/components/shared/top-rating-product-card.tsx +1 -1
- package/src/components-v2/BazaarImage.tsx +1 -1
- package/src/components-v2/BazaarMenu.tsx +2 -2
- package/src/components-v2/BazaarTextField.tsx +3 -3
- package/src/components-v2/BorderShadowCard.stories.tsx +1 -1
- package/src/components-v2/DropZone.tsx +1 -2
- package/src/components-v2/Typography.tsx +12 -12
- package/src/components-v2/accordion/accordion-header.tsx +1 -1
- package/src/components-v2/categories/category-list/styles.ts +0 -7
- package/src/components-v2/categories/category-list-item/category-menu-item.tsx +1 -1
- package/src/components-v2/categories/category-menu.tsx +16 -13
- package/src/components-v2/categories/mega-menu/mega-menu-1.tsx +1 -1
- package/src/components-v2/categories/mega-menu/mega-menu-2.tsx +2 -2
- package/src/components-v2/header/components/user.tsx +5 -5
- package/src/components-v2/mini-cart/__tests__/cart-item.test.tsx +13 -13
- package/src/components-v2/mini-cart/components/bottom-actions.tsx +1 -1
- package/src/components-v2/mini-cart/components/cart-item.tsx +3 -3
- package/src/components-v2/mini-cart/components/top-header.tsx +2 -2
- package/src/components-v2/mobile-navigation/mobile-navigation-bar.tsx +1 -1
- package/src/components-v2/nav-link/nav-link-2.tsx +1 -1
- package/src/components-v2/nav-link/nav-link-3.tsx +1 -1
- package/src/components-v2/nav-link/nav-link.tsx +1 -1
- package/src/components-v2/navbar/categories.tsx +3 -1
- package/src/components-v2/navbar/category-based-menu/category-based-menu.tsx +2 -2
- package/src/components-v2/navbar/mobile-menu/mobile-menu.tsx +11 -3
- package/src/components-v2/navbar/nav-list/nav-item-child.tsx +1 -1
- package/src/components-v2/navbar/nav-list/nav-list.tsx +118 -80
- package/src/components-v2/navbar/navbar.tsx +1 -1
- package/src/components-v2/no-records.tsx +1 -1
- package/src/components-v2/product-cards/favorite-button.tsx +1 -1
- package/src/components-v2/product-cards/product-card/product-card.tsx +5 -7
- package/src/components-v2/product-cards/product-card-plp/product-card.tsx +7 -6
- package/src/components-v2/product-cards/product-card-plp-list/components/tags.tsx +1 -1
- package/src/components-v2/product-cards/product-card-plp-list/product-card.tsx +7 -4
- package/src/components-v2/product-cards/product-card-search/product-card.tsx +4 -2
- package/src/components-v2/product-cards/product-price.tsx +1 -1
- package/src/components-v2/product-cards/product-rating.tsx +1 -1
- package/src/components-v2/product-cards/product-title.tsx +1 -1
- package/src/components-v2/product-cards/quantity-buttons.tsx +1 -1
- package/src/components-v2/product-dialog/compound/product-dialog.tsx +2 -2
- package/src/components-v2/product-dialog/index.tsx +1 -1
- package/src/components-v2/product-dimensions/compound/dimensions-group-list.tsx +6 -1
- package/src/components-v2/product-dimensions/index.tsx +3 -3
- package/src/components-v2/product-quantity-buttons/compound/quantity-buttons-root.tsx +1 -1
- package/src/components-v2/product-quantity-variants/index.tsx +1 -2
- package/src/components-v2/products-view/ProductsList.stories.tsx +0 -2
- package/src/components-v2/products-view/compound/products-grid-view.tsx +2 -2
- package/src/components-v2/products-view/compound/products-list-view.tsx +3 -3
- package/src/components-v2/products-view/index.tsx +2 -2
- package/src/components-v2/progress/progress.tsx +1 -1
- package/src/components-v2/scrollbar/scrollbar.tsx +1 -1
- package/src/components-v2/search-bar/compound/results-suspense.tsx +1 -2
- package/src/components-v2/search-bar/compound/results.tsx +6 -3
- package/src/components-v2/search-bar/compound/search-bar-root.tsx +4 -2
- package/src/components-v2/search-bar/compound/textfield-adornment-category.tsx +25 -27
- package/src/components-v2/search-bar/compound/textfield.tsx +0 -1
- package/src/components-v2/search-bar/hooks/use-search.ts +1 -1
- package/src/components-v2/section/Section.stories.tsx +1 -1
- package/src/components-v2/side-nav/side-nav.tsx +1 -1
- package/src/components-v2/wishlist-dialogs/add-to-wishlist/compound/add-to-wishlist-dialog.tsx +1 -1
- package/src/components-v2/wishlist-dialogs/add-to-wishlist/compound/add-to-wishlist-list.tsx +2 -2
- package/src/components-v2/wishlist-dialogs/add-to-wishlist/hooks/use-add-to-wishlist.ts +2 -0
- package/src/components-v2/wishlist-dialogs/add-to-wishlist/index.tsx +11 -13
- package/src/components-v2/wishlist-dialogs/create-wishlist/compound/create-wishlist-dialog.tsx +2 -1
- package/src/hooks/use-variants.ts +25 -27
- package/src/lib/build-detail-url.ts +1 -1
- package/src/lib/category-helpers.ts +3 -3
- package/src/lib/get-palette-color.ts +35 -15
- package/src/lib/hexToLuma.ts +2 -2
- package/src/lib/store-hours-helper.ts +5 -5
- package/src/pages-v2/account/addresses/address-form.tsx +10 -8
- package/src/pages-v2/account/addresses/address-item.tsx +1 -1
- package/src/pages-v2/account/orders/order-progress.tsx +1 -1
- package/src/pages-v2/account/orders/ordered-products.tsx +1 -1
- package/src/pages-v2/account/profile/user-info.tsx +1 -2
- package/src/pages-v2/account/wishlist/create-new-list.tsx +1 -0
- package/src/pages-v2/account/wishlist/wishlist-item.tsx +2 -1
- package/src/pages-v2/blog/blog-card.tsx +1 -1
- package/src/pages-v2/cart/__tests__/cart-item.test.tsx +0 -1
- package/src/pages-v2/cart/__tests__/checkout-form.test.tsx +0 -8
- package/src/pages-v2/cart/cart-item.tsx +86 -77
- package/src/pages-v2/cart/checkout-form.tsx +4 -4
- package/src/pages-v2/cart/coupon-entry.tsx +3 -3
- package/src/pages-v2/cart/estimate-shipping.tsx +6 -6
- package/src/pages-v2/checkout/checkout-alt-form/checkout-form.tsx +2 -2
- package/src/pages-v2/checkout/checkout-alt-form/checkout-step.tsx +2 -2
- package/src/pages-v2/checkout/checkout-alt-form/step-heading.tsx +1 -1
- package/src/pages-v2/checkout/checkout-alt-form/steps/address/delivery-address.tsx +12 -10
- package/src/pages-v2/checkout/checkout-alt-form/steps/address/new-address-form.tsx +5 -6
- package/src/pages-v2/checkout/checkout-alt-form/steps/customer-info/customer-information.tsx +8 -9
- package/src/pages-v2/checkout/checkout-alt-form/steps/payment/payment-details.tsx +17 -20
- package/src/pages-v2/checkout/checkout-alt-form/steps/shipping/delivery-date.tsx +1 -1
- package/src/pages-v2/checkout/checkout-alt-form/steps/shipping/delivery-options.tsx +14 -15
- package/src/pages-v2/confirmation/confirmation-summary.tsx +8 -7
- package/src/pages-v2/confirmation/ordered-products.tsx +3 -3
- package/src/pages-v2/product-details/bopis/pickup-option-select.tsx +1 -1
- package/src/pages-v2/product-details/bopis/store-card/shop-card.tsx +1 -1
- package/src/pages-v2/product-details/checkbox-label.tsx +1 -1
- package/src/pages-v2/product-details/frequently-bought.tsx +1 -1
- package/src/pages-v2/product-details/frequently-product-card.tsx +1 -1
- package/src/pages-v2/product-details/product-comment.tsx +1 -1
- package/src/pages-v2/product-details/product-description.tsx +2 -2
- package/src/pages-v2/product-details/product-filter-card.tsx +4 -1
- package/src/pages-v2/product-details/product-intro/compound/product-intro-images.tsx +36 -31
- package/src/pages-v2/product-details/product-intro/compound/product-intro.tsx +1 -1
- package/src/pages-v2/product-details/product-intro/compound/thumbnail-with-skeleton.tsx +1 -1
- package/src/pages-v2/product-details/product-intro/utils.ts +2 -4
- package/src/pages-v2/product-details/product-review.tsx +1 -1
- package/src/pages-v2/product-details/product-tabs.tsx +5 -2
- package/src/pages-v2/product-details/related-products.tsx +7 -4
- package/src/pages-v2/product-list/breadcrumbs.tsx +1 -1
- package/src/pages-v2/product-list/checkbox-label.tsx +1 -1
- package/src/pages-v2/product-list/facet-group.tsx +1 -1
- package/src/pages-v2/product-list/list-filter.tsx +2 -2
- package/src/pages-v2/product-list/product-list-context.tsx +5 -1
- package/src/pages-v2/product-list/product-list-view.tsx +4 -1
- package/src/pages-v2/product-list/product-rating.tsx +4 -1
- package/src/pages-v2/product-list/quick-view-dialog-content.tsx +2 -4
- package/src/pages-v2/product-list/quick-view-dialog.tsx +5 -1
- package/src/pages-v2/product-list/range-filter.tsx +0 -1
- package/src/pages-v2/product-list/scrollbar.tsx +1 -1
- package/src/pages-v2/product-list/side-nav.tsx +1 -1
- package/src/pages-v2/product-list/swatch.tsx +12 -7
- package/src/pages-v2/product-list/utils/getCategoryFilterFromSlug.ts +33 -68
- package/src/pages-v2/product-list/utils/product-list-helper.ts +1 -1
- package/src/pages-v2/product-list/utils/search-for-category.ts +2 -2
- package/src/pages-v2/quickorder/order-upload.tsx +90 -76
- package/src/pages-v2/quickorder/provider.tsx +6 -4
- package/src/pages-v2/quickorder/quick-order-row.tsx +1 -1
- package/src/pages-v2/quickorder/quick-order.tsx +28 -21
- package/src/providers/notifications/notification-context.tsx +0 -1
|
@@ -10,6 +10,23 @@ export type Category = {
|
|
|
10
10
|
Children?: Category[];
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
const matchesSlug = (cat: Category, slug: string) => {
|
|
14
|
+
const url = cat?.Url?.toLowerCase() || "";
|
|
15
|
+
const name = cat?.Name?.toLowerCase() || "";
|
|
16
|
+
const neutralName = cat?.NeutralizedNAME?.toLowerCase() || "";
|
|
17
|
+
const seoCrumb = cat?.seoCrumb?.toLowerCase() || "";
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
url.includes(slug) ||
|
|
21
|
+
name === slug ||
|
|
22
|
+
neutralName === slug ||
|
|
23
|
+
seoCrumb === slug
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const getCategoryId = (cat: Category, isD365: boolean) =>
|
|
28
|
+
isD365 ? cat.RecordId?.toString() : cat.id?.toString();
|
|
29
|
+
|
|
13
30
|
export function getCategoryIdFromSlug(
|
|
14
31
|
slug: string[],
|
|
15
32
|
categories: Category[],
|
|
@@ -23,18 +40,9 @@ export function getCategoryIdFromSlug(
|
|
|
23
40
|
const parentSlug = slug[0]?.toLowerCase();
|
|
24
41
|
const childSlug = slug[1]?.toLowerCase();
|
|
25
42
|
|
|
26
|
-
const parentCategory = categories.find((cat) =>
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const neutralName = cat?.NeutralizedNAME?.toLowerCase() || "";
|
|
30
|
-
const seoCrumb = cat?.seoCrumb?.toLowerCase() || "";
|
|
31
|
-
return (
|
|
32
|
-
url.includes(parentSlug) ||
|
|
33
|
-
name === parentSlug ||
|
|
34
|
-
neutralName === parentSlug ||
|
|
35
|
-
seoCrumb === parentSlug
|
|
36
|
-
);
|
|
37
|
-
});
|
|
43
|
+
const parentCategory = categories.find((cat) =>
|
|
44
|
+
matchesSlug(cat, parentSlug),
|
|
45
|
+
);
|
|
38
46
|
|
|
39
47
|
if (!parentCategory) {
|
|
40
48
|
console.warn(
|
|
@@ -44,74 +52,30 @@ export function getCategoryIdFromSlug(
|
|
|
44
52
|
return undefined;
|
|
45
53
|
}
|
|
46
54
|
|
|
47
|
-
const childMatch = parentCategory.Children?.find((cat) =>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const neutralName = cat?.NeutralizedNAME?.toLowerCase() || "";
|
|
51
|
-
const seoCrumb = cat?.seoCrumb?.toLowerCase() || "";
|
|
52
|
-
return (
|
|
53
|
-
url.includes(childSlug) ||
|
|
54
|
-
name === childSlug ||
|
|
55
|
-
neutralName === childSlug ||
|
|
56
|
-
seoCrumb === childSlug
|
|
57
|
-
);
|
|
58
|
-
});
|
|
59
|
-
if (childMatch) {
|
|
60
|
-
return isD365
|
|
61
|
-
? childMatch.RecordId?.toString()
|
|
62
|
-
: childMatch.id?.toString();
|
|
63
|
-
}
|
|
55
|
+
const childMatch = parentCategory.Children?.find((cat) =>
|
|
56
|
+
matchesSlug(cat, childSlug),
|
|
57
|
+
);
|
|
64
58
|
|
|
65
|
-
return undefined;
|
|
59
|
+
return childMatch ? getCategoryId(childMatch, isD365) : undefined;
|
|
66
60
|
}
|
|
67
61
|
|
|
68
62
|
const slugValue = slug[0]?.toLowerCase();
|
|
69
63
|
|
|
70
|
-
const topLevelMatch = categories.find((cat) =>
|
|
71
|
-
const url = cat?.Url?.toLowerCase() || "";
|
|
72
|
-
const name = cat?.Name?.toLowerCase() || "";
|
|
73
|
-
const neutralName = cat?.NeutralizedNAME?.toLowerCase() || "";
|
|
74
|
-
const seoCrumb = cat?.seoCrumb?.toLowerCase() || "";
|
|
75
|
-
return (
|
|
76
|
-
url.includes(slugValue) ||
|
|
77
|
-
name === slugValue ||
|
|
78
|
-
neutralName === slugValue ||
|
|
79
|
-
seoCrumb === slugValue
|
|
80
|
-
);
|
|
81
|
-
});
|
|
64
|
+
const topLevelMatch = categories.find((cat) => matchesSlug(cat, slugValue));
|
|
82
65
|
|
|
83
66
|
if (topLevelMatch) {
|
|
84
|
-
return isD365
|
|
85
|
-
? topLevelMatch.RecordId?.toString()
|
|
86
|
-
: topLevelMatch.id?.toString();
|
|
67
|
+
return getCategoryId(topLevelMatch, isD365);
|
|
87
68
|
}
|
|
88
69
|
|
|
89
|
-
let childMatch: Category | undefined;
|
|
90
70
|
for (const topLevelCat of categories) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const seoCrumb = cat?.seoCrumb?.toLowerCase() || "";
|
|
97
|
-
return (
|
|
98
|
-
url.includes(slugValue) ||
|
|
99
|
-
name === slugValue ||
|
|
100
|
-
neutralName === slugValue ||
|
|
101
|
-
seoCrumb === slugValue
|
|
102
|
-
);
|
|
103
|
-
});
|
|
104
|
-
if (child) {
|
|
105
|
-
childMatch = child;
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
71
|
+
const child = topLevelCat.Children?.find((cat) =>
|
|
72
|
+
matchesSlug(cat, slugValue),
|
|
73
|
+
);
|
|
74
|
+
if (child) {
|
|
75
|
+
return getCategoryId(child, isD365);
|
|
108
76
|
}
|
|
109
77
|
}
|
|
110
78
|
|
|
111
|
-
if (childMatch) {
|
|
112
|
-
return isD365 ? childMatch.RecordId?.toString() : childMatch.id?.toString();
|
|
113
|
-
}
|
|
114
|
-
|
|
115
79
|
return undefined;
|
|
116
80
|
}
|
|
117
81
|
|
|
@@ -142,7 +106,8 @@ export function getCategoryFilterFromSlug(
|
|
|
142
106
|
});
|
|
143
107
|
if (children.length > 0) {
|
|
144
108
|
return children
|
|
145
|
-
.map((child) =>
|
|
109
|
+
.map((child) => child.seoCrumb || child.Url || child.Name)
|
|
110
|
+
.filter(Boolean)
|
|
146
111
|
.join("|");
|
|
147
112
|
}
|
|
148
113
|
}
|
|
@@ -53,11 +53,11 @@ const getCategoryTranslatedName = (
|
|
|
53
53
|
(item) => item.Language?.toLowerCase() === locale.toLowerCase(),
|
|
54
54
|
);
|
|
55
55
|
}
|
|
56
|
-
return nameTranslation
|
|
56
|
+
return nameTranslation?.Text;
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
const normalizeCategoryName = (name: string) =>
|
|
60
|
-
name.toLowerCase().
|
|
60
|
+
name.toLowerCase().replaceAll(" ", "-");
|
|
61
61
|
|
|
62
62
|
const getCategoryUrl = (category: CategoryHierarchy) => {
|
|
63
63
|
const contentPageKey = PageSlug.Content;
|
|
@@ -15,11 +15,14 @@ export interface OrderUploadProps {
|
|
|
15
15
|
onUploadItems: (data: OrderUploadItem[]) => void;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
// eslint-disable-next-line
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
19
|
declare const XLSX: any;
|
|
20
20
|
|
|
21
|
-
export default function OrderUpload({
|
|
21
|
+
export default function OrderUpload({
|
|
22
|
+
onUploadItems,
|
|
23
|
+
}: Readonly<OrderUploadProps>) {
|
|
22
24
|
const t = useTranslations("QuickOrder");
|
|
25
|
+
|
|
23
26
|
const [items, setItems] = useState<OrderUploadItem[]>([]);
|
|
24
27
|
const [errCount, setErrCount] = useState<number>(0);
|
|
25
28
|
const [files, setFiles] = useState<File[]>();
|
|
@@ -27,13 +30,28 @@ export default function OrderUpload({ onUploadItems }: OrderUploadProps) {
|
|
|
27
30
|
|
|
28
31
|
const accept: Accept = { "text/*": [".xlsx", ".csv", ".xls"] };
|
|
29
32
|
|
|
33
|
+
useEffect(loadXlsxScript, []);
|
|
34
|
+
|
|
30
35
|
useEffect(() => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
if (!files) return;
|
|
37
|
+
|
|
38
|
+
const processFiles = async () => {
|
|
39
|
+
for (const file of files) {
|
|
40
|
+
const buffer = await file.arrayBuffer();
|
|
41
|
+
|
|
42
|
+
const sheet = readFirstSheet(buffer);
|
|
43
|
+
const parsed = extractItemsFromSheet(sheet);
|
|
44
|
+
|
|
45
|
+
const { validItems, errors } = validateItems(parsed, items);
|
|
46
|
+
|
|
47
|
+
setItems(validItems);
|
|
48
|
+
setErrCount(errCount + errors);
|
|
49
|
+
onUploadItems(validItems);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
processFiles();
|
|
54
|
+
}, [files, items, onUploadItems]);
|
|
37
55
|
|
|
38
56
|
const handleReject = (rejs: FileRejection[]) => {
|
|
39
57
|
const msgs = rejs.map((r) => `${r.file.name} ${t("fileRejectionMsg")}`);
|
|
@@ -53,82 +71,16 @@ export default function OrderUpload({ onUploadItems }: OrderUploadProps) {
|
|
|
53
71
|
|
|
54
72
|
const handleCloseError = () => setFileRejections([]);
|
|
55
73
|
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
const handleUploadItems = (list: OrderUploadItem[]) => onUploadItems(list);
|
|
58
|
-
|
|
59
|
-
const parseFile = (file: File) => {
|
|
60
|
-
const currItems = items ? [...items] : [];
|
|
61
|
-
let currErrCount = errCount;
|
|
62
|
-
|
|
63
|
-
const reader = new FileReader();
|
|
64
|
-
reader.onload = (evt) => {
|
|
65
|
-
const bstr = evt.target?.result;
|
|
66
|
-
const wb = XLSX.read(bstr, { type: "binary" });
|
|
67
|
-
const wsname = wb.SheetNames[0];
|
|
68
|
-
const ws = wb.Sheets[wsname];
|
|
69
|
-
const ref = ws["!ref"];
|
|
70
|
-
let data: OrderUploadItem[] = [];
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
if (ref) {
|
|
74
|
-
const spl = ref.split(":");
|
|
75
|
-
if (spl.length === 2) {
|
|
76
|
-
const rowCount = Number(spl[1].substring(1));
|
|
77
|
-
for (let i = 2; i < rowCount + 1; i++) {
|
|
78
|
-
const qty = ws[`B${i}`]?.v;
|
|
79
|
-
const itemId = ws[`A${i}`]?.v;
|
|
80
|
-
if (itemId) data.push({ itemId, qty });
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
} catch {}
|
|
85
|
-
|
|
86
|
-
if (data.length === 0) {
|
|
87
|
-
data = XLSX.utils.sheet_to_json(ws, {
|
|
88
|
-
header: 0,
|
|
89
|
-
}) as OrderUploadItem[];
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
data.forEach((d) => {
|
|
93
|
-
const qty =
|
|
94
|
-
typeof d.qty === "string" || d.qty - Math.floor(d.qty) !== 0
|
|
95
|
-
? 1
|
|
96
|
-
: d.qty;
|
|
97
|
-
if (d.itemId && d.qty) currItems.push({ qty, itemId: d.itemId });
|
|
98
|
-
else currErrCount++;
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
setItems(currItems);
|
|
102
|
-
setErrCount(currErrCount);
|
|
103
|
-
handleUploadItems(currItems);
|
|
104
|
-
};
|
|
105
|
-
reader.readAsBinaryString(file);
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const isValidFile = (file: File): boolean => {
|
|
109
|
-
parseFile(file);
|
|
110
|
-
return file.type === "text/csv";
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
if (files) {
|
|
114
|
-
files.forEach((file) => {
|
|
115
|
-
if (isValidFile(file)) {
|
|
116
|
-
// noop
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
121
|
-
}, [items, files, errCount, onUploadItems]);
|
|
122
|
-
|
|
123
74
|
return (
|
|
124
75
|
<>
|
|
125
76
|
{fileRejections.length > 0 && (
|
|
126
77
|
<Alert severity="error" onClose={handleCloseError}>
|
|
127
78
|
{fileRejections.map((msg, i) => (
|
|
128
|
-
<div key={i}>{msg}</div>
|
|
79
|
+
<div key={`e-msg-${i + 1}`}>{msg}</div>
|
|
129
80
|
))}
|
|
130
81
|
</Alert>
|
|
131
82
|
)}
|
|
83
|
+
|
|
132
84
|
<DropZone
|
|
133
85
|
onChange={getFiles}
|
|
134
86
|
handleReject={handleReject}
|
|
@@ -137,3 +89,65 @@ export default function OrderUpload({ onUploadItems }: OrderUploadProps) {
|
|
|
137
89
|
</>
|
|
138
90
|
);
|
|
139
91
|
}
|
|
92
|
+
|
|
93
|
+
// Loading xlsx script from CDN, as the library doesn't support ESM imports yet
|
|
94
|
+
function loadXlsxScript() {
|
|
95
|
+
const script = document.createElement("script");
|
|
96
|
+
script.lang = "javascript";
|
|
97
|
+
script.src =
|
|
98
|
+
"https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js";
|
|
99
|
+
document.head.appendChild(script);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
//Read Excel Sheet
|
|
103
|
+
function readFirstSheet(binary: string | ArrayBuffer) {
|
|
104
|
+
const wb = XLSX.read(binary, { type: "binary" });
|
|
105
|
+
const wsname = wb.SheetNames[0];
|
|
106
|
+
return wb.Sheets[wsname];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
//Extract Items
|
|
110
|
+
// biome-ignore lint/suspicious/noExplicitAny: <explanation> This is required as the XLSX library returns a sheet as an object with dynamic keys based on the cell positions (e.g., A1, B2, etc.), which cannot be typed more specifically without extensive type definitions that may not be practical. --- IGNORE ---
|
|
111
|
+
function extractItemsFromSheet(ws: Record<string, any>): OrderUploadItem[] {
|
|
112
|
+
const ref = ws["!ref"];
|
|
113
|
+
let data: OrderUploadItem[] = [];
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
if (ref) {
|
|
117
|
+
const [_, end] = ref.split(":");
|
|
118
|
+
const rowCount = Number(end.substring(1));
|
|
119
|
+
|
|
120
|
+
for (let i = 2; i < rowCount + 1; i++) {
|
|
121
|
+
const qty = ws[`B${i}`]?.v;
|
|
122
|
+
const itemId = ws[`A${i}`]?.v;
|
|
123
|
+
|
|
124
|
+
if (itemId) data.push({ itemId, qty });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} catch {}
|
|
128
|
+
|
|
129
|
+
if (data.length === 0) {
|
|
130
|
+
data = XLSX.utils.sheet_to_json(ws, { header: 0 });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return data;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
//Validate Items
|
|
137
|
+
function validateItems(data: OrderUploadItem[], existing: OrderUploadItem[]) {
|
|
138
|
+
const validItems = [...existing];
|
|
139
|
+
let errors = 0;
|
|
140
|
+
|
|
141
|
+
data.forEach((d) => {
|
|
142
|
+
const qty =
|
|
143
|
+
typeof d.qty === "string" || d.qty - Math.floor(d.qty) !== 0 ? 1 : d.qty;
|
|
144
|
+
|
|
145
|
+
if (d.itemId && d.qty) {
|
|
146
|
+
validItems.push({ itemId: d.itemId, qty });
|
|
147
|
+
} else {
|
|
148
|
+
errors++;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return { validItems, errors };
|
|
153
|
+
}
|
|
@@ -11,7 +11,7 @@ import type { QuickOrderRowData } from "./quick-order-row";
|
|
|
11
11
|
export default function QuickOrderProvider({
|
|
12
12
|
children,
|
|
13
13
|
}: {
|
|
14
|
-
children: React.ReactNode;
|
|
14
|
+
readonly children: React.ReactNode;
|
|
15
15
|
}) {
|
|
16
16
|
const [rows, setRows] = useState<QuickOrderRowData[]>([]);
|
|
17
17
|
const { mutateAsync: searchProducts } = useSearchProducts();
|
|
@@ -84,11 +84,11 @@ export default function QuickOrderProvider({
|
|
|
84
84
|
if (updated[idx].value !== value) {
|
|
85
85
|
updated[idx].value = value;
|
|
86
86
|
const response = await getProductSearchResults([value]);
|
|
87
|
-
product =
|
|
87
|
+
product = response?.[0] || null;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
updated[idx] = generateRowData(
|
|
91
|
-
product
|
|
91
|
+
product || null,
|
|
92
92
|
id,
|
|
93
93
|
value,
|
|
94
94
|
!!product,
|
|
@@ -173,12 +173,14 @@ export default function QuickOrderProvider({
|
|
|
173
173
|
header +
|
|
174
174
|
rows.map(({ value, quantity }) => `${value},${quantity}`).join("\n");
|
|
175
175
|
const uri = "data:text/csv;charset=utf-8," + csv;
|
|
176
|
+
|
|
176
177
|
const link = document.createElement("a");
|
|
177
178
|
link.setAttribute("href", encodeURI(uri));
|
|
178
179
|
link.setAttribute("download", "quick_order.csv");
|
|
180
|
+
|
|
179
181
|
document.body.appendChild(link);
|
|
180
182
|
link.click();
|
|
181
|
-
|
|
183
|
+
link.remove();
|
|
182
184
|
};
|
|
183
185
|
|
|
184
186
|
useEffect(() => initTable(), [initTable]);
|
|
@@ -47,7 +47,7 @@ export default function QuickOrderRow({
|
|
|
47
47
|
onRowChange,
|
|
48
48
|
isLastRow,
|
|
49
49
|
rowError,
|
|
50
|
-
}: QuickOrderRowProps) {
|
|
50
|
+
}: Readonly<QuickOrderRowProps>) {
|
|
51
51
|
const theme = useTheme();
|
|
52
52
|
const t = useTranslations("QuickOrder");
|
|
53
53
|
const { formatCurrency } = useCurrencyFormatter();
|
|
@@ -98,7 +98,7 @@ const QuickOrder = ({ children }: { children?: ReactNode }) => {
|
|
|
98
98
|
const idx = current.findIndex((r) => r.id === id);
|
|
99
99
|
if (idx > -1) {
|
|
100
100
|
current.splice(idx, 1);
|
|
101
|
-
const newRowId = current.
|
|
101
|
+
const newRowId = (current.at(-1)?.id ?? 0) + 1;
|
|
102
102
|
if (current.length < 3) current.push(generateEmptyRow(newRowId));
|
|
103
103
|
setRows(current);
|
|
104
104
|
}
|
|
@@ -167,7 +167,7 @@ const QuickOrder = ({ children }: { children?: ReactNode }) => {
|
|
|
167
167
|
if (current[idx].value !== value) {
|
|
168
168
|
current[idx].value = value;
|
|
169
169
|
const response = await searchProductByRow(id, value);
|
|
170
|
-
product =
|
|
170
|
+
product = response?.[0] || null;
|
|
171
171
|
found = !!product;
|
|
172
172
|
}
|
|
173
173
|
|
|
@@ -177,7 +177,7 @@ const QuickOrder = ({ children }: { children?: ReactNode }) => {
|
|
|
177
177
|
|
|
178
178
|
const addRows = () => {
|
|
179
179
|
const current = [...rows];
|
|
180
|
-
const maxRowId = current.reduce((m, r) => (
|
|
180
|
+
const maxRowId = current.reduce((m, r) => Math.max(m, r.id), 0);
|
|
181
181
|
const newRowId = maxRowId + 1;
|
|
182
182
|
current.push(generateEmptyRow(newRowId), generateEmptyRow(newRowId + 1));
|
|
183
183
|
setRows(current);
|
|
@@ -190,12 +190,13 @@ const QuickOrder = ({ children }: { children?: ReactNode }) => {
|
|
|
190
190
|
header +
|
|
191
191
|
rows.map(({ value, quantity }) => `${value},${quantity}`).join("\n");
|
|
192
192
|
const encoded = encodeURI("data:text/csv;charset=utf-8," + csv);
|
|
193
|
+
|
|
193
194
|
const link = document.createElement("a");
|
|
194
195
|
link.setAttribute("href", encoded);
|
|
195
196
|
link.setAttribute("download", "quick_order.csv");
|
|
196
197
|
document.body.appendChild(link);
|
|
197
198
|
link.click();
|
|
198
|
-
|
|
199
|
+
link.remove();
|
|
199
200
|
};
|
|
200
201
|
|
|
201
202
|
const addToCart = async () => {
|
|
@@ -225,7 +226,7 @@ const QuickOrder = ({ children }: { children?: ReactNode }) => {
|
|
|
225
226
|
const firstEmptyIdx = curr.findIndex((r) => !r.value);
|
|
226
227
|
if (firstEmptyIdx > -1) curr = curr.slice(0, firstEmptyIdx);
|
|
227
228
|
|
|
228
|
-
let rowId = curr.reduce((m, r) => (
|
|
229
|
+
let rowId = curr.reduce((m, r) => Math.max(m, r.id), 0) + 1;
|
|
229
230
|
|
|
230
231
|
items.forEach((it) => {
|
|
231
232
|
const product = response?.find(
|
|
@@ -325,6 +326,24 @@ QuickOrder.Row = ({
|
|
|
325
326
|
}) => {
|
|
326
327
|
const ctx = useQuickOrder();
|
|
327
328
|
const theme = useTheme();
|
|
329
|
+
const t = useTranslations("QuickOrder");
|
|
330
|
+
const { formatCurrency } = useCurrencyFormatter();
|
|
331
|
+
|
|
332
|
+
const [inputValue, setInputValue] = useState<string>(row.value || "");
|
|
333
|
+
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
setInputValue(row.value || "");
|
|
336
|
+
}, [row.value]);
|
|
337
|
+
|
|
338
|
+
const debounced = useMemo(
|
|
339
|
+
() =>
|
|
340
|
+
_.debounce(
|
|
341
|
+
(val: string) => ctx?.updateRow(row.id, val, row.quantity),
|
|
342
|
+
500,
|
|
343
|
+
),
|
|
344
|
+
[ctx, row.id, row.quantity],
|
|
345
|
+
);
|
|
346
|
+
|
|
328
347
|
if (!ctx) return null;
|
|
329
348
|
|
|
330
349
|
const getStatus = (
|
|
@@ -344,22 +363,6 @@ QuickOrder.Row = ({
|
|
|
344
363
|
}
|
|
345
364
|
};
|
|
346
365
|
|
|
347
|
-
const t = useTranslations("QuickOrder");
|
|
348
|
-
const { formatCurrency } = useCurrencyFormatter();
|
|
349
|
-
|
|
350
|
-
const [inputValue, setInputValue] = useState<string>(row.value || "");
|
|
351
|
-
|
|
352
|
-
useEffect(() => setInputValue(row.value || ""), [row.value]);
|
|
353
|
-
|
|
354
|
-
const debounced = useMemo(
|
|
355
|
-
() =>
|
|
356
|
-
_.debounce(
|
|
357
|
-
(val: string) => ctx.updateRow(row.id, val, row.quantity),
|
|
358
|
-
500,
|
|
359
|
-
),
|
|
360
|
-
[ctx, row.id, row.quantity],
|
|
361
|
-
);
|
|
362
|
-
|
|
363
366
|
const onValueChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
364
367
|
setInputValue(e.target.value);
|
|
365
368
|
debounced(e.target.value);
|
|
@@ -389,6 +392,7 @@ QuickOrder.Row = ({
|
|
|
389
392
|
placeholder={t("Table.itemNo")}
|
|
390
393
|
/>
|
|
391
394
|
</TableCell>
|
|
395
|
+
|
|
392
396
|
<TableCell sx={{ width: { xs: "20%" } }}>
|
|
393
397
|
<QuickOrder.QtyInput
|
|
394
398
|
value={row.quantity}
|
|
@@ -397,10 +401,13 @@ QuickOrder.Row = ({
|
|
|
397
401
|
placeholder={t("Table.quantity")}
|
|
398
402
|
/>
|
|
399
403
|
</TableCell>
|
|
404
|
+
|
|
400
405
|
<TableCell>{getStatus(row.product, row.rowError, t)}</TableCell>
|
|
406
|
+
|
|
401
407
|
<TableCell sx={{ display: { xs: "none", sm: "table-cell" } }}>
|
|
402
408
|
{row.product?.Price ? formatCurrency(row.product.Price) : ""}
|
|
403
409
|
</TableCell>
|
|
410
|
+
|
|
404
411
|
<TableCell>
|
|
405
412
|
<Button
|
|
406
413
|
variant="contained"
|