@evenicanpm/storefront-core 1.3.0 → 1.6.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.
Files changed (188) hide show
  1. package/__mocks__/get-product-by-id.ts +0 -1
  2. package/__mocks__/query-client.ts +1 -1
  3. package/package.json +3 -3
  4. package/src/api-manager/datasources/d365/d365-address.datasource.ts +2 -2
  5. package/src/api-manager/datasources/d365/d365-cart.datasource.ts +4 -5
  6. package/src/api-manager/datasources/d365/d365-categories.datasource.ts +1 -1
  7. package/src/api-manager/datasources/d365/d365-product.datasource.ts +21 -7
  8. package/src/api-manager/datasources/d365/d365-session.datasource.ts +5 -7
  9. package/src/api-manager/datasources/d365/d365-user.datasource.ts +12 -23
  10. package/src/api-manager/datasources/d365/d365.datasource.ts +0 -2
  11. package/src/api-manager/datasources/d365/index.ts +2 -4
  12. package/src/api-manager/datasources/d365/utils/decode-jwt.ts +1 -2
  13. package/src/api-manager/datasources/d365/utils/parse-object.ts +1 -1
  14. package/src/api-manager/datasources/e4/address/e4-address.remaps.ts +2 -2
  15. package/src/api-manager/datasources/e4/address/e4-address.translator.ts +1 -1
  16. package/src/api-manager/datasources/e4/cart/e4-cart.datasource.ts +28 -27
  17. package/src/api-manager/datasources/e4/cart/e4-cart.remaps.ts +1 -1
  18. package/src/api-manager/datasources/e4/cart/e4-cart.translator.ts +1 -1
  19. package/src/api-manager/datasources/e4/categories/e4-categories.translator.ts +20 -20
  20. package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +471 -7
  21. package/src/api-manager/datasources/e4/order/e4-order.remaps.ts +2 -2
  22. package/src/api-manager/datasources/e4/organization/e4-organization.datasource.ts +2 -8
  23. package/src/api-manager/datasources/e4/product/e4-product.remaps.ts +3 -3
  24. package/src/api-manager/datasources/e4/product/e4-product.translator.ts +13 -29
  25. package/src/api-manager/datasources/e4/session/e4-session.datasource.ts +1 -3
  26. package/src/api-manager/datasources/e4/user/e4-user.remaps.ts +2 -4
  27. package/src/api-manager/datasources/e4/utils/unwrap-e4-variants.ts +4 -4
  28. package/src/api-manager/index.ts +1 -1
  29. package/src/api-manager/lib/category-helpers.ts +3 -2
  30. package/src/api-manager/lib/get-graphql-client.ts +1 -2
  31. package/src/api-manager/services/cart/mutations/add-discount-code.ts +2 -2
  32. package/src/api-manager/services/cart/mutations/add-to-cart.ts +2 -2
  33. package/src/api-manager/services/product/server/product.server.ts +1 -1
  34. package/src/api-manager/types/Datasource.ts +4 -9
  35. package/src/auth/auth-options.test.ts +2 -58
  36. package/src/auth/auth-options.ts +1 -2
  37. package/src/auth/next-auth-cookie-manager.ts +0 -2
  38. package/src/auth/refresh-token.ts +3 -8
  39. package/src/auth/signout.ts +1 -1
  40. package/src/cms/blocks/block-manager.tsx +3 -3
  41. package/src/cms/blocks/components/category-card.tsx +1 -1
  42. package/src/cms/blocks/components/category-carousel/index.tsx +3 -1
  43. package/src/cms/blocks/components/footer/index.tsx +2 -2
  44. package/src/cms/blocks/components/footer/sections/footer-links.tsx +2 -2
  45. package/src/cms/blocks/components/footer/sections/footer-logo.tsx +1 -1
  46. package/src/cms/blocks/components/footer/sections/footer-social-links.tsx +10 -2
  47. package/src/cms/blocks/components/hero-carousel/index.tsx +29 -25
  48. package/src/cms/blocks/components/media-card.tsx +1 -1
  49. package/src/cms/blocks/components/product-carousel/index.tsx +1 -1
  50. package/src/cms/blocks/components/product-section-fullwidth/index.tsx +2 -2
  51. package/src/cms/blocks/components/seo/index.tsx +5 -1
  52. package/src/cms/blocks/components/services/index.tsx +1 -1
  53. package/src/cms/blocks/components/services/service-card.tsx +1 -1
  54. package/src/cms/blocks/components/shared/featured-product-card.tsx +4 -1
  55. package/src/cms/blocks/components/shared/product-category-item.tsx +1 -1
  56. package/src/cms/blocks/components/shared/product-grid.tsx +4 -1
  57. package/src/cms/blocks/components/shared/top-categories-card.tsx +5 -1
  58. package/src/cms/blocks/components/shared/top-rating-product-card.tsx +1 -1
  59. package/src/components-v2/BazaarImage.tsx +1 -1
  60. package/src/components-v2/BazaarMenu.tsx +2 -2
  61. package/src/components-v2/BazaarTextField.tsx +3 -3
  62. package/src/components-v2/BorderShadowCard.stories.tsx +1 -1
  63. package/src/components-v2/DropZone.tsx +1 -2
  64. package/src/components-v2/Typography.tsx +12 -12
  65. package/src/components-v2/accordion/accordion-header.tsx +1 -1
  66. package/src/components-v2/categories/category-list/styles.ts +0 -7
  67. package/src/components-v2/categories/category-list-item/category-menu-item.tsx +1 -1
  68. package/src/components-v2/categories/category-menu.tsx +16 -13
  69. package/src/components-v2/categories/mega-menu/mega-menu-1.tsx +1 -1
  70. package/src/components-v2/categories/mega-menu/mega-menu-2.tsx +2 -2
  71. package/src/components-v2/header/components/user.tsx +5 -5
  72. package/src/components-v2/mini-cart/__tests__/cart-item.test.tsx +13 -13
  73. package/src/components-v2/mini-cart/components/bottom-actions.tsx +1 -1
  74. package/src/components-v2/mini-cart/components/cart-item.tsx +3 -3
  75. package/src/components-v2/mini-cart/components/top-header.tsx +2 -2
  76. package/src/components-v2/mobile-navigation/mobile-navigation-bar.tsx +1 -1
  77. package/src/components-v2/nav-link/nav-link-2.tsx +1 -1
  78. package/src/components-v2/nav-link/nav-link-3.tsx +1 -1
  79. package/src/components-v2/nav-link/nav-link.tsx +1 -1
  80. package/src/components-v2/navbar/categories.tsx +3 -1
  81. package/src/components-v2/navbar/category-based-menu/category-based-menu.tsx +2 -2
  82. package/src/components-v2/navbar/mobile-menu/mobile-menu.tsx +11 -3
  83. package/src/components-v2/navbar/nav-list/nav-item-child.tsx +1 -1
  84. package/src/components-v2/navbar/nav-list/nav-list.tsx +118 -80
  85. package/src/components-v2/navbar/navbar.tsx +1 -1
  86. package/src/components-v2/no-records.tsx +1 -1
  87. package/src/components-v2/product-cards/favorite-button.tsx +1 -1
  88. package/src/components-v2/product-cards/product-card/product-card.tsx +5 -7
  89. package/src/components-v2/product-cards/product-card-plp/product-card.tsx +7 -6
  90. package/src/components-v2/product-cards/product-card-plp-list/components/tags.tsx +1 -1
  91. package/src/components-v2/product-cards/product-card-plp-list/product-card.tsx +7 -4
  92. package/src/components-v2/product-cards/product-card-search/product-card.tsx +4 -2
  93. package/src/components-v2/product-cards/product-price.tsx +1 -1
  94. package/src/components-v2/product-cards/product-rating.tsx +1 -1
  95. package/src/components-v2/product-cards/product-title.tsx +1 -1
  96. package/src/components-v2/product-cards/quantity-buttons.tsx +1 -1
  97. package/src/components-v2/product-dialog/compound/product-dialog.tsx +2 -2
  98. package/src/components-v2/product-dialog/index.tsx +1 -1
  99. package/src/components-v2/product-dimensions/compound/dimensions-group-list.tsx +6 -1
  100. package/src/components-v2/product-dimensions/index.tsx +3 -3
  101. package/src/components-v2/product-quantity-buttons/compound/quantity-buttons-root.tsx +1 -1
  102. package/src/components-v2/product-quantity-variants/index.tsx +1 -2
  103. package/src/components-v2/products-view/ProductsList.stories.tsx +0 -2
  104. package/src/components-v2/products-view/compound/products-grid-view.tsx +2 -2
  105. package/src/components-v2/products-view/compound/products-list-view.tsx +3 -3
  106. package/src/components-v2/products-view/index.tsx +2 -2
  107. package/src/components-v2/progress/progress.tsx +1 -1
  108. package/src/components-v2/scrollbar/scrollbar.tsx +1 -1
  109. package/src/components-v2/search-bar/compound/results-suspense.tsx +1 -2
  110. package/src/components-v2/search-bar/compound/results.tsx +6 -3
  111. package/src/components-v2/search-bar/compound/search-bar-root.tsx +4 -2
  112. package/src/components-v2/search-bar/compound/textfield-adornment-category.tsx +25 -27
  113. package/src/components-v2/search-bar/compound/textfield.tsx +0 -1
  114. package/src/components-v2/search-bar/hooks/use-search.ts +1 -1
  115. package/src/components-v2/section/Section.stories.tsx +1 -1
  116. package/src/components-v2/side-nav/side-nav.tsx +1 -1
  117. package/src/components-v2/wishlist-dialogs/add-to-wishlist/compound/add-to-wishlist-dialog.tsx +1 -1
  118. package/src/components-v2/wishlist-dialogs/add-to-wishlist/compound/add-to-wishlist-list.tsx +2 -2
  119. package/src/components-v2/wishlist-dialogs/add-to-wishlist/hooks/use-add-to-wishlist.ts +2 -0
  120. package/src/components-v2/wishlist-dialogs/add-to-wishlist/index.tsx +11 -13
  121. package/src/components-v2/wishlist-dialogs/create-wishlist/compound/create-wishlist-dialog.tsx +2 -1
  122. package/src/hooks/use-variants.ts +25 -27
  123. package/src/lib/build-detail-url.ts +1 -1
  124. package/src/lib/category-helpers.ts +3 -3
  125. package/src/lib/get-palette-color.ts +35 -15
  126. package/src/lib/hexToLuma.ts +2 -2
  127. package/src/lib/store-hours-helper.ts +5 -5
  128. package/src/pages-v2/account/addresses/address-form.tsx +10 -8
  129. package/src/pages-v2/account/addresses/address-item.tsx +1 -1
  130. package/src/pages-v2/account/orders/order-progress.tsx +1 -1
  131. package/src/pages-v2/account/orders/ordered-products.tsx +1 -1
  132. package/src/pages-v2/account/profile/user-info.tsx +1 -2
  133. package/src/pages-v2/account/wishlist/create-new-list.tsx +1 -0
  134. package/src/pages-v2/account/wishlist/wishlist-item.tsx +2 -1
  135. package/src/pages-v2/blog/blog-card.tsx +1 -1
  136. package/src/pages-v2/cart/__tests__/cart-item.test.tsx +0 -1
  137. package/src/pages-v2/cart/__tests__/checkout-form.test.tsx +0 -8
  138. package/src/pages-v2/cart/cart-item.tsx +86 -77
  139. package/src/pages-v2/cart/checkout-form.tsx +4 -4
  140. package/src/pages-v2/cart/coupon-entry.tsx +3 -3
  141. package/src/pages-v2/cart/estimate-shipping.tsx +6 -6
  142. package/src/pages-v2/checkout/checkout-alt-form/checkout-form.tsx +2 -2
  143. package/src/pages-v2/checkout/checkout-alt-form/checkout-step.tsx +2 -2
  144. package/src/pages-v2/checkout/checkout-alt-form/step-heading.tsx +1 -1
  145. package/src/pages-v2/checkout/checkout-alt-form/steps/address/delivery-address.tsx +12 -10
  146. package/src/pages-v2/checkout/checkout-alt-form/steps/address/new-address-form.tsx +5 -6
  147. package/src/pages-v2/checkout/checkout-alt-form/steps/customer-info/customer-information.tsx +8 -9
  148. package/src/pages-v2/checkout/checkout-alt-form/steps/payment/payment-details.tsx +17 -20
  149. package/src/pages-v2/checkout/checkout-alt-form/steps/shipping/delivery-date.tsx +1 -1
  150. package/src/pages-v2/checkout/checkout-alt-form/steps/shipping/delivery-options.tsx +14 -15
  151. package/src/pages-v2/confirmation/confirmation-summary.tsx +8 -7
  152. package/src/pages-v2/confirmation/ordered-products.tsx +3 -3
  153. package/src/pages-v2/product-details/bopis/pickup-option-select.tsx +1 -1
  154. package/src/pages-v2/product-details/bopis/store-card/shop-card.tsx +1 -1
  155. package/src/pages-v2/product-details/checkbox-label.tsx +1 -1
  156. package/src/pages-v2/product-details/frequently-bought.tsx +1 -1
  157. package/src/pages-v2/product-details/frequently-product-card.tsx +1 -1
  158. package/src/pages-v2/product-details/product-comment.tsx +1 -1
  159. package/src/pages-v2/product-details/product-description.tsx +2 -2
  160. package/src/pages-v2/product-details/product-filter-card.tsx +4 -1
  161. package/src/pages-v2/product-details/product-intro/compound/product-intro-images.tsx +36 -31
  162. package/src/pages-v2/product-details/product-intro/compound/product-intro.tsx +1 -1
  163. package/src/pages-v2/product-details/product-intro/compound/thumbnail-with-skeleton.tsx +1 -1
  164. package/src/pages-v2/product-details/product-intro/utils.ts +2 -4
  165. package/src/pages-v2/product-details/product-review.tsx +1 -1
  166. package/src/pages-v2/product-details/product-tabs.tsx +5 -2
  167. package/src/pages-v2/product-details/related-products.tsx +7 -4
  168. package/src/pages-v2/product-list/breadcrumbs.tsx +1 -1
  169. package/src/pages-v2/product-list/checkbox-label.tsx +1 -1
  170. package/src/pages-v2/product-list/facet-group.tsx +1 -1
  171. package/src/pages-v2/product-list/list-filter.tsx +2 -2
  172. package/src/pages-v2/product-list/product-list-context.tsx +5 -1
  173. package/src/pages-v2/product-list/product-list-view.tsx +4 -1
  174. package/src/pages-v2/product-list/product-rating.tsx +4 -1
  175. package/src/pages-v2/product-list/quick-view-dialog-content.tsx +2 -4
  176. package/src/pages-v2/product-list/quick-view-dialog.tsx +5 -1
  177. package/src/pages-v2/product-list/range-filter.tsx +0 -1
  178. package/src/pages-v2/product-list/scrollbar.tsx +1 -1
  179. package/src/pages-v2/product-list/side-nav.tsx +1 -1
  180. package/src/pages-v2/product-list/swatch.tsx +12 -7
  181. package/src/pages-v2/product-list/utils/getCategoryFilterFromSlug.ts +33 -68
  182. package/src/pages-v2/product-list/utils/product-list-helper.ts +1 -1
  183. package/src/pages-v2/product-list/utils/search-for-category.ts +2 -2
  184. package/src/pages-v2/quickorder/order-upload.tsx +90 -76
  185. package/src/pages-v2/quickorder/provider.tsx +6 -4
  186. package/src/pages-v2/quickorder/quick-order-row.tsx +1 -1
  187. package/src/pages-v2/quickorder/quick-order.tsx +28 -21
  188. 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
- const url = cat?.Url?.toLowerCase() || "";
28
- const name = cat?.Name?.toLowerCase() || "";
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
- const url = cat?.Url?.toLowerCase() || "";
49
- const name = cat?.Name?.toLowerCase() || "";
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
- if (topLevelCat.Children && topLevelCat.Children.length > 0) {
92
- const child = topLevelCat.Children.find((cat) => {
93
- const url = cat?.Url?.toLowerCase() || "";
94
- const name = cat?.Name?.toLowerCase() || "";
95
- const neutralName = cat?.NeutralizedNAME?.toLowerCase() || "";
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) => (child.seoCrumb || child.Url || child.Name) as string)
109
+ .map((child) => child.seoCrumb || child.Url || child.Name)
110
+ .filter(Boolean)
146
111
  .join("|");
147
112
  }
148
113
  }
@@ -42,7 +42,7 @@ const getSelectedSortOption = (
42
42
  const getCategoryFromSlug = (slug: string[]) => {
43
43
  if (Array.isArray(slug)) {
44
44
  const categoryId = Number(slug.pop());
45
- if (!isNaN(categoryId)) {
45
+ if (!Number.isNaN(categoryId)) {
46
46
  return categoryId;
47
47
  }
48
48
  }
@@ -53,11 +53,11 @@ const getCategoryTranslatedName = (
53
53
  (item) => item.Language?.toLowerCase() === locale.toLowerCase(),
54
54
  );
55
55
  }
56
- return nameTranslation && nameTranslation.Text;
56
+ return nameTranslation?.Text;
57
57
  };
58
58
 
59
59
  const normalizeCategoryName = (name: string) =>
60
- name.toLowerCase().replace(/ /g, "-");
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({ onUploadItems }: OrderUploadProps) {
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
- const script = document.createElement("script");
32
- script.lang = "javascript";
33
- script.src =
34
- "https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js";
35
- document.head.appendChild(script);
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 = (response && response[0]) || null;
87
+ product = response?.[0] || null;
88
88
  }
89
89
 
90
90
  updated[idx] = generateRowData(
91
- product ? product : null,
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
- document.body.removeChild(link);
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.length ? current[current.length - 1].id + 1 : 1;
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 = (response && response[0]) || null;
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) => (r.id > m ? r.id : m), 0);
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
- document.body.removeChild(link);
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) => (r.id > m ? r.id : m), 0) + 1;
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"
@@ -33,7 +33,6 @@ export const NotificationProvider = ({
33
33
  severity: "info",
34
34
  type: "general",
35
35
  });
36
- // const [miniCartOpen, setMiniCartOpen] = React.useState<boolean>(false);
37
36
 
38
37
  const setNotification = (notification: NotificationType) => {
39
38
  setAlert(notification);