@evenicanpm/storefront-core 1.0.0 → 1.0.1

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 (133) hide show
  1. package/package.json +3 -2
  2. package/src/api-manager/datasources/d365/d365-address.datasource.ts +1 -1
  3. package/src/api-manager/datasources/d365/d365-user.datasource.ts +0 -19
  4. package/src/api-manager/datasources/e4/e4-cart.datasource.ts +2 -2
  5. package/src/api-manager/datasources/e4/e4-product.datasource.ts +19 -4
  6. package/src/api-manager/datasources/e4/e4.datasource.ts +1 -2
  7. package/src/api-manager/datasources/e4/e4.remaps.ts +24 -0
  8. package/src/api-manager/datasources/e4/e4.translator.ts +17 -0
  9. package/src/api-manager/datasources/e4/{e4-user.datasource.ts → user/e4-user.datasource.ts} +5 -7
  10. package/src/api-manager/datasources/e4/user/e4-user.remaps.ts +4 -0
  11. package/src/api-manager/datasources/e4/user/e4-user.translator.ts +10 -0
  12. package/src/api-manager/index.ts +21 -6
  13. package/src/api-manager/schemas/product.schema.ts +1 -1
  14. package/src/api-manager/services/address/queries/get-states.ts +1 -1
  15. package/src/api-manager/services/create-query.ts +10 -3
  16. package/src/api-manager/types/Datasource.ts +9 -9
  17. package/src/components/Blocks/Components/ProductCarousel/index.tsx +6 -6
  18. package/src/components/_components/navbar/category-based-menu/components/categories.tsx +1 -1
  19. package/src/components/_components/navbar/mega-menu/mega-menu.tsx +1 -1
  20. package/src/components/_components/navbar/nav-list/nav-item-child.tsx +2 -2
  21. package/src/components/_components/product-dimensions/product-dimensions.tsx +114 -0
  22. package/src/components/_components/product-quantity-buttons/product-quantity-buttons.tsx +88 -0
  23. package/src/components/_components/section-header/section-header.tsx +1 -1
  24. package/src/components/_components/settings/setting.tsx +1 -1
  25. package/src/components/_components/wishlist-modal/TODO.md +10 -0
  26. package/src/components/_components/wishlist-modal/add-to-wishlist-button.tsx +68 -0
  27. package/src/components/_components/wishlist-modal/add-to-wishlist-dialog.tsx +161 -0
  28. package/src/components/_components/wishlist-modal/remove-from-wishlist-dialog.tsx +174 -0
  29. package/src/components/_components/wishlist-modal/wishlist-modal-item.tsx +65 -0
  30. package/src/components/countries-input.tsx +1 -0
  31. package/src/hooks/use-cart.tsx +82 -0
  32. package/src/hooks/use-variants.tsx +85 -0
  33. package/src/pages/account/addresses/address-form.tsx +289 -0
  34. package/src/pages/account/addresses/address-item.tsx +104 -0
  35. package/src/pages/account/dashboard-header.tsx +123 -0
  36. package/src/pages/account/navigation.tsx +84 -0
  37. package/src/pages/account/no-records.tsx +20 -0
  38. package/src/pages/account/orders/icons/delivery.tsx +14 -0
  39. package/src/pages/account/orders/icons/package-box.tsx +13 -0
  40. package/src/pages/account/orders/icons/truck-filled.tsx +14 -0
  41. package/src/pages/account/orders/order-progress.tsx +111 -0
  42. package/src/pages/account/orders/order-row.tsx +61 -0
  43. package/src/pages/account/orders/order-summary.tsx +83 -0
  44. package/src/pages/account/orders/ordered-products.tsx +114 -0
  45. package/src/pages/account/profile/profile-button.test.tsx +59 -0
  46. package/src/pages/account/profile/profile-button.tsx +50 -0
  47. package/src/pages/account/profile/user-info.tsx +59 -0
  48. package/src/pages/account/styles.ts +32 -0
  49. package/src/pages/account/table-row.tsx +19 -0
  50. package/src/pages/account/wishlist/create-new-list.tsx +167 -0
  51. package/src/pages/account/wishlist/create-wishlist-button.tsx +40 -0
  52. package/src/pages/account/wishlist/wishlist-item.tsx +82 -0
  53. package/src/pages/blog/pagination.tsx +38 -0
  54. package/src/pages/cart/cart-item.tsx +312 -0
  55. package/src/pages/cart/checkout-form.tsx +122 -0
  56. package/src/pages/cart/coupon-entry.tsx +90 -0
  57. package/src/pages/cart/estimate-shipping.tsx +183 -0
  58. package/src/pages/cart/wrapper.tsx +30 -0
  59. package/src/pages/checkout/checkout-alt-form/address-card.tsx +73 -0
  60. package/src/pages/checkout/checkout-alt-form/checkout-form.tsx +133 -0
  61. package/src/pages/checkout/checkout-alt-form/checkout-step.tsx +120 -0
  62. package/src/pages/checkout/checkout-alt-form/customer-information.tsx +94 -0
  63. package/src/pages/checkout/checkout-alt-form/delivery-address.tsx +170 -0
  64. package/src/pages/checkout/checkout-alt-form/delivery-date.tsx +114 -0
  65. package/src/pages/checkout/checkout-alt-form/delivery-options.tsx +186 -0
  66. package/src/pages/checkout/checkout-alt-form/edit-address-form.tsx +130 -0
  67. package/src/pages/checkout/checkout-alt-form/heading.tsx +50 -0
  68. package/src/pages/checkout/checkout-alt-form/index.ts +1 -0
  69. package/src/pages/checkout/checkout-alt-form/new-address-form.tsx +294 -0
  70. package/src/pages/checkout/checkout-alt-form/node.tsx +9 -0
  71. package/src/pages/checkout/checkout-alt-form/payment-details.tsx +344 -0
  72. package/src/pages/checkout/checkout-alt-form/safe-card-preview-data.tsx +36 -0
  73. package/src/pages/checkout/checkout-alt-form/types.ts +20 -0
  74. package/src/pages/checkout/checkout-alt-summary/cart-item.tsx +39 -0
  75. package/src/pages/checkout/checkout-alt-summary/checkout-alt-summary.tsx +40 -0
  76. package/src/pages/checkout/checkout-alt-summary/index.ts +1 -0
  77. package/src/pages/checkout/checkout-alt-summary/list-item.tsx +31 -0
  78. package/src/pages/confirmation/address.tsx +22 -0
  79. package/src/pages/confirmation/confirmation-summary.tsx +52 -0
  80. package/src/pages/confirmation/ordered-products.tsx +108 -0
  81. package/src/pages/product-details/available-shops.tsx +48 -0
  82. package/src/pages/product-details/bopis/find-in-store-button.tsx +63 -0
  83. package/src/pages/product-details/bopis/find-in-store-modal.tsx +257 -0
  84. package/src/pages/product-details/bopis/pickup-option-select.tsx +127 -0
  85. package/src/pages/product-details/bopis/search-header.tsx +69 -0
  86. package/src/pages/product-details/checkbox-label.tsx +20 -0
  87. package/src/pages/product-details/currency.ts +64 -0
  88. package/src/pages/product-details/frequently-bought.tsx +90 -0
  89. package/src/pages/product-details/frequently-product-card.tsx +60 -0
  90. package/src/pages/product-details/product-comment.tsx +44 -0
  91. package/src/pages/product-details/product-description.tsx +22 -0
  92. package/src/pages/product-details/product-filter-card.tsx +257 -0
  93. package/src/pages/product-details/product-intro/product-intro-images.tsx +87 -0
  94. package/src/pages/product-details/product-intro/product-intro.tsx +250 -0
  95. package/src/pages/product-details/product-review.tsx +131 -0
  96. package/src/pages/product-details/product-tabs.tsx +51 -0
  97. package/src/pages/product-details/related-products.tsx +42 -0
  98. package/src/pages/product-details/types.ts +11 -0
  99. package/src/pages/product-list/breadcrumbs.tsx +39 -0
  100. package/src/pages/product-list/checkbox-label.tsx +20 -0
  101. package/src/pages/product-list/facet-group.tsx +125 -0
  102. package/src/pages/product-list/facet.tsx +36 -0
  103. package/src/pages/product-list/list-filter.tsx +40 -0
  104. package/src/pages/product-list/pagination.tsx +80 -0
  105. package/src/pages/product-list/product-list-context.tsx +302 -0
  106. package/src/pages/product-list/product-list-state.ts +187 -0
  107. package/src/pages/product-list/product-rating.tsx +16 -0
  108. package/src/pages/product-list/quick-view-dialog-content.tsx +205 -0
  109. package/src/pages/product-list/quick-view-dialog.tsx +54 -0
  110. package/src/pages/product-list/range-filter.tsx +125 -0
  111. package/src/pages/product-list/scrollbar.tsx +38 -0
  112. package/src/pages/product-list/search-bar.tsx +162 -0
  113. package/src/pages/product-list/selected-facets.tsx +80 -0
  114. package/src/pages/product-list/side-nav.tsx +49 -0
  115. package/src/pages/product-list/swatch.tsx +80 -0
  116. package/src/pages/product-list/types.ts +18 -0
  117. package/src/pages/product-list/use-product-list.ts +12 -0
  118. package/src/pages/product-list/useCategoryFilter.tsx +26 -0
  119. package/src/pages/product-list/utils/facet-helpers.ts +5 -0
  120. package/src/pages/product-list/utils/generate-breadcrumbs.ts +30 -0
  121. package/src/pages/product-list/utils/getCategoryFilterFromSlug.ts +153 -0
  122. package/src/pages/product-list/utils/product-list-helper.ts +111 -0
  123. package/src/pages/product-list/utils/product-list-types.ts +41 -0
  124. package/src/pages/product-list/utils/search-for-category.ts +76 -0
  125. package/src/pages/product-list/utils/sort-options.ts +44 -0
  126. package/src/pages/product-list/utils/use-previous-refiners.ts +14 -0
  127. package/src/pages/quickorder/order-upload.tsx +150 -0
  128. package/src/pages/quickorder/quick-order-form.tsx +343 -0
  129. package/src/pages/quickorder/quick-order-row.tsx +144 -0
  130. package/tsconfig.json +1 -0
  131. package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +0 -10464
  132. /package/src/{components/_components/hooks/useOverflowDetect.ts → hooks/use-overflow-detect.ts} +0 -0
  133. /package/src/{components/_components/hooks/useSettings.ts → hooks/use-settings.ts} +0 -0
@@ -0,0 +1,150 @@
1
+ import DropZone from "@/components/_components/DropZone";
2
+ import { Alert } from "@mui/material";
3
+ import { useEffect, useState } from "react";
4
+ import { Accept, FileRejection } from "react-dropzone/.";
5
+
6
+ export interface OrderUploadItem {
7
+ itemId: string;
8
+ qty: number;
9
+ }
10
+
11
+ export interface OrderUploadProps {
12
+ onUploadItems: (data: OrderUploadItem[]) => void;
13
+ }
14
+
15
+ // TODO: fix type
16
+ // eslint-disable-next-line
17
+ declare const XLSX: any;
18
+
19
+ export default function OrderUpload({ onUploadItems }: OrderUploadProps) {
20
+ const [items, setItems] = useState<OrderUploadItem[]>([]);
21
+ const [errCount, setErrCount] = useState<number>(0);
22
+ const [files, setFiles] = useState<File[]>();
23
+ const [fileRejections, setFileRejections] = useState<string[]>([]);
24
+
25
+ const accept: Accept = {
26
+ "text/*": [".xlsx", ".csv", ".xls"],
27
+ };
28
+
29
+ useEffect(() => {
30
+ const script = document.createElement("script");
31
+ script.lang = "javascript";
32
+ script.src =
33
+ "https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js";
34
+ document.head.appendChild(script);
35
+ }, []);
36
+
37
+ const handleReject = (fileRejections: FileRejection[]) => {
38
+ const rejections = fileRejections.map(
39
+ (r) => `${r.file.name} {t('QuickOrder.fileRejectionMsg')}`,
40
+ );
41
+ setFileRejections(rejections);
42
+ };
43
+
44
+ const resetStates = () => {
45
+ setItems([]);
46
+ setErrCount(0);
47
+ setFileRejections([]);
48
+ };
49
+
50
+ const getFiles = <T extends File>(files: T[]) => {
51
+ resetStates();
52
+ setFiles(files);
53
+ };
54
+
55
+ const handleCloseError = () => {
56
+ setFileRejections([]);
57
+ };
58
+
59
+ useEffect(() => {
60
+ const handleUploadItems = (items: OrderUploadItem[]) => {
61
+ onUploadItems(items);
62
+ };
63
+
64
+ const parseFile = (file: File) => {
65
+ const currItems = items || [];
66
+ let currErrCount = errCount;
67
+ const reader = new FileReader();
68
+ reader.onload = (evt) => {
69
+ const bstr = evt.target?.result;
70
+ const wb = XLSX.read(bstr, { type: "binary" });
71
+ const wsname = wb.SheetNames[0];
72
+ const ws = wb.Sheets[wsname];
73
+ const ref = ws["!ref"];
74
+ let data: OrderUploadItem[] = [];
75
+ try {
76
+ if (ref) {
77
+ const spl = ref.split(":");
78
+ if (spl.length === 2) {
79
+ const rowCount = Number(spl[1].substring(1));
80
+ for (let i = 2; i < rowCount + 1; i++) {
81
+ const qty = ws[`B${i}`]?.v;
82
+ const itemId = ws[`A${i}`]?.v;
83
+ if (itemId) {
84
+ data.push({
85
+ itemId,
86
+ qty,
87
+ });
88
+ }
89
+ }
90
+ }
91
+ }
92
+ } catch (ex) {
93
+ // Do nothing
94
+ }
95
+ if (data.length === 0) {
96
+ data = XLSX.utils.sheet_to_json(ws, {
97
+ header: 0,
98
+ }) as OrderUploadItem[];
99
+ }
100
+ data.forEach((d) => {
101
+ const qty =
102
+ typeof d.qty === "string" || d.qty - Math.floor(d.qty) !== 0
103
+ ? 1
104
+ : d.qty;
105
+ if (d.itemId && d.qty) {
106
+ currItems.push({ qty: qty, itemId: d.itemId });
107
+ } else {
108
+ currErrCount++;
109
+ }
110
+ });
111
+ setItems(currItems);
112
+ setErrCount(currErrCount);
113
+ handleUploadItems(currItems);
114
+ };
115
+ reader.readAsBinaryString(file);
116
+ };
117
+
118
+ const isValidFile = (file: File): boolean => {
119
+ parseFile(file);
120
+ return file.type === "text/csv";
121
+ };
122
+
123
+ if (files) {
124
+ files.forEach((file) => {
125
+ if (isValidFile(file)) {
126
+ //console.log("Valid!");
127
+ }
128
+ });
129
+ }
130
+ // TODO: fix dependency
131
+ //eslint-disable-next-line
132
+ }, [items, files, errCount]);
133
+
134
+ return (
135
+ <>
136
+ {fileRejections.length > 0 && (
137
+ <Alert severity="error" onClose={handleCloseError}>
138
+ {fileRejections.map((msg, index) => (
139
+ <div key={index}>{msg}</div>
140
+ ))}
141
+ </Alert>
142
+ )}
143
+ <DropZone
144
+ onChange={getFiles}
145
+ handleReject={handleReject}
146
+ accept={accept}
147
+ />
148
+ </>
149
+ );
150
+ }
@@ -0,0 +1,343 @@
1
+ "use client";
2
+
3
+ import { FlexBox } from "@/components/_components/flex-box/index";
4
+ import {
5
+ Button,
6
+ Container,
7
+ Table,
8
+ TableBody,
9
+ TableCell,
10
+ TableContainer,
11
+ TableHead,
12
+ TableRow,
13
+ } from "@mui/material";
14
+ import QuickOrderRow, { QuickOrderRowData } from "./quick-order-row";
15
+ import { useCallback, useEffect, useState } from "react";
16
+ import { ProductSearchResult } from "@msdyn365-commerce/retail-proxy";
17
+ import useSearchProducts from "@/api-manager/services/product/mutations/search-products";
18
+ import { BorderShadowCard } from "@/components/BoxShadowCard";
19
+ import { H2, Span } from "@/components/_components/Typography";
20
+ import useAddToCart from "@/api-manager/services/cart/mutations/add-to-cart";
21
+ import OrderUpload, { OrderUploadItem } from "./order-upload";
22
+ import { useTranslations } from "next-intl";
23
+ import getCart from "@/api-manager/services/cart/queries/get-cart";
24
+
25
+ export enum RowError {
26
+ ProductNotFound,
27
+ InvalidQuantity,
28
+ OutOfStock,
29
+ None,
30
+ }
31
+
32
+ export default function QuickOrderForm() {
33
+ const [rows, setRows] = useState<QuickOrderRowData[]>([]);
34
+ const { mutateAsync: searchProducts } = useSearchProducts();
35
+ const { mutateAsync: addToCart } = useAddToCart();
36
+ const { data: cart } = getCart.useData();
37
+
38
+ const t = useTranslations("QuickOrder");
39
+
40
+ const _generateEmptyRow = (id: number) => {
41
+ return {
42
+ id,
43
+ value: "",
44
+ quantity: "1",
45
+ product: null,
46
+ rowError: RowError.None,
47
+ };
48
+ };
49
+
50
+ // initialize the table with 3 empty rows
51
+ const _tableInit = useCallback(() => {
52
+ const numEmptyRows = 3;
53
+ const rows: QuickOrderRowData[] = [];
54
+ for (let i = 0; i < numEmptyRows; i++) {
55
+ rows.push(_generateEmptyRow(i + 1));
56
+ }
57
+ setRows(rows);
58
+ }, []);
59
+
60
+ const _handleRowDelete = (id: number) => {
61
+ const currentRows = [...rows];
62
+ const idx = currentRows.findIndex((r) => r.id === id);
63
+ if (idx > -1) {
64
+ currentRows.splice(idx, 1);
65
+ const newRowId = currentRows[currentRows.length - 1].id + 1;
66
+ if (currentRows.length < 3) {
67
+ currentRows.push(_generateEmptyRow(newRowId));
68
+ }
69
+ setRows(currentRows);
70
+ }
71
+ };
72
+
73
+ const _handleRowUpdate = async (
74
+ id: number,
75
+ value: string,
76
+ quantity: string,
77
+ ) => {
78
+ const currentRows = [...rows];
79
+ const idx = currentRows.findIndex((row) => row.id === id);
80
+
81
+ // if value changes and is beyond the min characters, do a search for the product
82
+ let product = currentRows[idx].product;
83
+ let foundProduct = false;
84
+ if (currentRows[idx].value !== value) {
85
+ currentRows[idx].value = value;
86
+ const response = await searchProductByRow(id, value);
87
+ product = (response && response[0]) || null;
88
+ }
89
+ foundProduct = !!product;
90
+ currentRows[idx] = _generateRowData(
91
+ product,
92
+ id,
93
+ value,
94
+ foundProduct,
95
+ quantity,
96
+ );
97
+ setRows(currentRows);
98
+ };
99
+
100
+ const searchProductByRow = async (_id: number, searchText: string) => {
101
+ // call getProductSearchResults with a single entry in an array
102
+ const minQueryLength = 5;
103
+ let response;
104
+ if (searchText.length >= minQueryLength) {
105
+ response = await getProductSearchResults([searchText]);
106
+ }
107
+ return response;
108
+ };
109
+
110
+ const getProductSearchResults = useCallback(
111
+ async (itemIds: string[]) => {
112
+ // API call to retrieve matching products from database
113
+ const results = await searchProducts({ itemIds });
114
+ return results;
115
+ },
116
+ [searchProducts],
117
+ );
118
+
119
+ const searchProductsMultiple = useCallback(
120
+ async (itemIds: string[]) => {
121
+ // call getProductSearchResults for all item ids
122
+ let response;
123
+ const minQueryLength = 5;
124
+ const validItemIds = itemIds.filter((it) => it.length >= minQueryLength);
125
+ if (validItemIds) {
126
+ response = await getProductSearchResults(validItemIds);
127
+ }
128
+ return response;
129
+ },
130
+ [getProductSearchResults],
131
+ );
132
+
133
+ const _generateRowData = useCallback(
134
+ (
135
+ product: ProductSearchResult | null,
136
+ rowId: number,
137
+ searchText: string,
138
+ foundProduct: boolean,
139
+ quantity: string,
140
+ ) => {
141
+ let rowError = RowError.None;
142
+ if (
143
+ Number.isNaN(Number(quantity)) ||
144
+ Number(quantity) < 1 ||
145
+ !Number.isInteger(Number(quantity))
146
+ ) {
147
+ rowError = RowError.InvalidQuantity;
148
+ } else if (!foundProduct) {
149
+ rowError = RowError.ProductNotFound;
150
+ }
151
+ // TODO: Out of Stock
152
+ const row: QuickOrderRowData = {
153
+ ..._generateEmptyRow(rowId),
154
+ product: foundProduct ? product : null,
155
+ value: searchText,
156
+ quantity: quantity.toString(),
157
+ rowError: rowError,
158
+ };
159
+ return row;
160
+ },
161
+ [],
162
+ );
163
+
164
+ const _handleAddRows = () => {
165
+ const currentRows = [...rows];
166
+
167
+ const maxRowId = currentRows.reduce(
168
+ (max, row) => (row.id > max ? row.id : max),
169
+ 0,
170
+ );
171
+ const newRowId = maxRowId + 1;
172
+ currentRows.push(_generateEmptyRow(newRowId));
173
+ currentRows.push(_generateEmptyRow(newRowId + 1));
174
+
175
+ setRows(currentRows);
176
+ };
177
+
178
+ const _generateFile = () => {
179
+ if (rows.length === 0) return;
180
+
181
+ const header = "SKU,Quantity\n";
182
+
183
+ // parse data into CSV format
184
+ const csvContent =
185
+ "data:text/csv;charset=utf-8," +
186
+ header +
187
+ rows.map(({ value, quantity }) => `${value},${quantity}`).join("\n");
188
+ const encodedUri = encodeURI(csvContent);
189
+ const link = document.createElement("a");
190
+ link.setAttribute("href", encodedUri);
191
+ link.setAttribute("download", "quick_order.csv");
192
+ document.body.appendChild(link);
193
+ link.click();
194
+ document.body.removeChild(link);
195
+ };
196
+
197
+ const _generateRow = (row: QuickOrderRowData, isLast: boolean) => {
198
+ return (
199
+ <QuickOrderRow
200
+ key={row.id}
201
+ id={row.id}
202
+ value={row.value}
203
+ quantity={row.quantity}
204
+ product={row.product}
205
+ onRowDelete={_handleRowDelete}
206
+ onRowChange={_handleRowUpdate}
207
+ onAddRows={_handleAddRows}
208
+ isLastRow={isLast}
209
+ rowError={row.rowError}
210
+ />
211
+ );
212
+ };
213
+
214
+ const handleAddToCart = async () => {
215
+ // add all rows that have a RowError of type None with requested quantities
216
+ if (!cart) return;
217
+
218
+ const products = rows
219
+ .filter((row) => row.product && row.rowError === RowError.None)
220
+ .map((productRow) => ({
221
+ ProductId: Number(productRow.product?.RecordId),
222
+ Quantity: Number(productRow.quantity),
223
+ }));
224
+ if (products.length > 0) {
225
+ await addToCart({
226
+ cartId: cart.Id,
227
+ version: cart.Version,
228
+ cartLine: products,
229
+ });
230
+ _tableInit();
231
+ }
232
+ };
233
+
234
+ const handleUploadedItems = useCallback(
235
+ async (items: OrderUploadItem[]) => {
236
+ const itemIds = items.map((it) => it.itemId.toString());
237
+ const response = await searchProductsMultiple(itemIds);
238
+
239
+ // add items to table
240
+ let currRows = [...rows];
241
+ const idx = currRows.findIndex((r) => !r.value);
242
+ if (idx > -1) {
243
+ currRows = currRows.slice(0, idx);
244
+ }
245
+ let rowId =
246
+ currRows.reduce((max, row) => (row.id > max ? row.id : max), 0) + 1;
247
+
248
+ items.forEach((it) => {
249
+ const product = response?.find((r) => {
250
+ const itemId = it.itemId.toString().toLowerCase();
251
+ const searchText = r.ItemId?.toLowerCase();
252
+ return itemId === searchText;
253
+ });
254
+ const qty = it.qty || 1;
255
+ const rowData = _generateRowData(
256
+ product || null,
257
+ rowId++,
258
+ it.itemId,
259
+ !!product,
260
+ qty.toString(),
261
+ );
262
+ currRows.push(rowData);
263
+ });
264
+ while (currRows.length < 3) {
265
+ currRows.push({ ..._generateEmptyRow(rowId++) });
266
+ }
267
+ setRows(currRows);
268
+ },
269
+ [_generateRowData, rows, searchProductsMultiple],
270
+ );
271
+
272
+ useEffect(() => {
273
+ _tableInit();
274
+ }, [_tableInit]);
275
+
276
+ return (
277
+ <Container>
278
+ <BorderShadowCard>
279
+ <FlexBox justifyContent="left" px={3}>
280
+ <H2>{t("title")}</H2>
281
+ </FlexBox>
282
+ <FlexBox justifyContent="left" px={3}>
283
+ <Span>{t("entryBySku")}</Span>
284
+ </FlexBox>
285
+ </BorderShadowCard>
286
+ <BorderShadowCard sx={{ padding: 0 }}>
287
+ <TableContainer sx={{ borderRadius: "4px" }}>
288
+ <Table>
289
+ <TableHead sx={{ backgroundColor: "#323337" }}>
290
+ <TableRow>
291
+ <TableCell sx={{ color: "white", width: { xs: "30%" } }}>
292
+ {t("Table.itemNo")}
293
+ </TableCell>
294
+ <TableCell sx={{ color: "white", width: { xs: "20%" } }}>
295
+ {t("Table.quantity")}
296
+ </TableCell>
297
+ <TableCell sx={{ color: "white" }}>
298
+ {t("Table.itemName")}
299
+ </TableCell>
300
+ <TableCell
301
+ sx={{
302
+ color: "white",
303
+ display: { xs: "none", sm: "table-cell" },
304
+ }}
305
+ >
306
+ {t("Table.price")}
307
+ </TableCell>
308
+ <TableCell sx={{ color: "white" }}>
309
+ {t("Table.delete")}
310
+ </TableCell>
311
+ </TableRow>
312
+ </TableHead>
313
+ <TableBody>
314
+ {rows.map((r, i) => _generateRow(r, i === rows.length - 1))}
315
+ </TableBody>
316
+ </Table>
317
+ </TableContainer>
318
+ </BorderShadowCard>
319
+ <BorderShadowCard>
320
+ <FlexBox sx={{ justifyContent: "center", gap: { xs: 2, sm: 20 } }}>
321
+ <Button
322
+ variant="contained"
323
+ color="secondary"
324
+ onClick={handleAddToCart}
325
+ >
326
+ {t("addToCartBtn")}
327
+ </Button>
328
+ <Button variant="contained" color="secondary" onClick={_tableInit}>
329
+ {t("resetFormBtn")}
330
+ </Button>
331
+ <Button variant="contained" color="secondary" onClick={_generateFile}>
332
+ {t("downloadFileBtn")}
333
+ </Button>
334
+ </FlexBox>
335
+ </BorderShadowCard>
336
+ <BorderShadowCard>
337
+ <FlexBox justifyContent="center" gap={2} flexDirection="column" px={2}>
338
+ <OrderUpload onUploadItems={handleUploadedItems} />
339
+ </FlexBox>
340
+ </BorderShadowCard>
341
+ </Container>
342
+ );
343
+ }
@@ -0,0 +1,144 @@
1
+ "use client";
2
+
3
+ import { ProductSearchResult } from "@msdyn365-commerce/retail-proxy";
4
+ import {
5
+ Button,
6
+ TableCell,
7
+ TableRow,
8
+ TextField,
9
+ useTheme,
10
+ } from "@mui/material";
11
+ import React, { useEffect, useMemo, useState } from "react";
12
+ import DeleteIcon from "@mui/icons-material/Delete";
13
+ import { RowError } from "./quick-order-form";
14
+ import { Span } from "@/components/_components/Typography";
15
+ import { useTranslations } from "next-intl";
16
+ import useCurrencyFormatter from "@/lib/currency-formatter";
17
+ import _ from "lodash";
18
+
19
+ export interface QuickOrderRowData {
20
+ id: number;
21
+ value: string;
22
+ quantity: string;
23
+ product: ProductSearchResult | null;
24
+ rowError: RowError;
25
+ }
26
+
27
+ interface QuickOrderRowProps {
28
+ id: number;
29
+ value: string;
30
+ quantity: string;
31
+ product: ProductSearchResult | null;
32
+ rowError: RowError;
33
+ onRowDelete: (id: number) => void;
34
+ onAddRows: () => void;
35
+ onRowChange: (id: number, value: string, quantity: string) => void;
36
+ isLastRow: boolean;
37
+ }
38
+
39
+ export default function QuickOrderRow({
40
+ id,
41
+ value,
42
+ quantity,
43
+ product,
44
+ onRowDelete,
45
+ onAddRows,
46
+ onRowChange,
47
+ isLastRow,
48
+ rowError,
49
+ }: QuickOrderRowProps) {
50
+ const theme = useTheme();
51
+ const t = useTranslations("QuickOrder");
52
+ const { formatCurrency } = useCurrencyFormatter();
53
+
54
+ const [inputValue, setInputValue] = useState<string>(value || "");
55
+
56
+ useEffect(() => {
57
+ setInputValue(value || "");
58
+ }, [value]);
59
+
60
+ const debouncedOnChange = useMemo(
61
+ () =>
62
+ _.debounce((val) => {
63
+ onRowChange(id, val, quantity);
64
+ }, 500),
65
+ [onRowChange, id, quantity],
66
+ );
67
+
68
+ const handleValueChange = (e: React.ChangeEvent<HTMLInputElement>) => {
69
+ setInputValue(e.target.value);
70
+ debouncedOnChange(e.target.value);
71
+ };
72
+
73
+ const handleQuantityChange = (e: React.ChangeEvent<HTMLInputElement>) => {
74
+ onRowChange(id, value, e.target.value);
75
+ };
76
+
77
+ const addRows = () => {
78
+ onAddRows();
79
+ };
80
+
81
+ const handleRowDelete = () => onRowDelete(id);
82
+
83
+ const getRowStatusMessage = (product: ProductSearchResult | null) => {
84
+ switch (rowError) {
85
+ case RowError.InvalidQuantity:
86
+ return t("invalidQuantity");
87
+ case RowError.ProductNotFound:
88
+ return t("productNotFound");
89
+ case RowError.OutOfStock:
90
+ return t("OOS");
91
+ }
92
+ if (product) {
93
+ return product.Name;
94
+ }
95
+ return null;
96
+ };
97
+
98
+ return (
99
+ <TableRow
100
+ sx={{
101
+ ...(rowError !== RowError.None && {
102
+ bgcolor: theme.palette.error.light,
103
+ }),
104
+ }}
105
+ >
106
+ <TableCell sx={{ width: { xs: "30%" } }}>
107
+ <TextField
108
+ value={inputValue}
109
+ InputProps={{ sx: { bgcolor: "white" } }}
110
+ onChange={handleValueChange}
111
+ onFocus={() => {
112
+ if (isLastRow && addRows) {
113
+ addRows();
114
+ }
115
+ }}
116
+ />
117
+ </TableCell>
118
+ <TableCell sx={{ width: { xs: "20%" } }}>
119
+ <TextField
120
+ value={quantity}
121
+ InputProps={{ sx: { bgcolor: "white" } }}
122
+ onChange={handleQuantityChange}
123
+ onFocus={() => {
124
+ if (isLastRow && addRows) {
125
+ addRows();
126
+ }
127
+ }}
128
+ />
129
+ </TableCell>
130
+ <TableCell>{getRowStatusMessage(product)}</TableCell>
131
+ <TableCell sx={{ display: { xs: "none", sm: "table-cell" } }}>
132
+ {product?.Price ? formatCurrency(product?.Price) : ""}
133
+ </TableCell>
134
+ <TableCell>
135
+ <Button variant="contained" color="secondary" onClick={handleRowDelete}>
136
+ <DeleteIcon />
137
+ <Span sx={{ display: { xs: "none", sm: "inline-flex" } }}>
138
+ {t("deleteBtn")}
139
+ </Span>
140
+ </Button>
141
+ </TableCell>
142
+ </TableRow>
143
+ );
144
+ }
package/tsconfig.json CHANGED
@@ -21,6 +21,7 @@
21
21
  "@/components/*": ["src/components/*"],
22
22
  "@/schemas/*": ["src/api-manager/schemas/*"],
23
23
  "@/services/*": ["src/api-manager/services/*"],
24
+ "@/hooks/*": ["src/hooks/*"],
24
25
  "@config": ["src/config"]
25
26
  }
26
27
  }