@nyris/nyris-webapp 0.3.97 → 0.3.99

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 (94) hide show
  1. package/build/asset-manifest.json +7 -6
  2. package/build/index.html +1 -1
  3. package/build/js/settings.example.js +41 -0
  4. package/build/static/css/main.5cb72c09.css +4 -0
  5. package/build/static/css/main.5cb72c09.css.map +1 -0
  6. package/build/static/js/main.2339271b.js +3 -0
  7. package/build/static/js/{main.9e7fb38a.js.map → main.2339271b.js.map} +1 -1
  8. package/build/static/media/plate_info.6beaea36d35053be3a45.png +0 -0
  9. package/package.json +3 -3
  10. package/public/js/settings.example.js +41 -0
  11. package/src/common/assets/images/plate_info.png +0 -0
  12. package/src/components/AuthApiKeySync.tsx +34 -0
  13. package/src/components/Cart.tsx +41 -0
  14. package/src/components/ChatAssistant/useChatAssistantLogic.ts +3 -2
  15. package/src/components/GoBackButton.tsx +29 -2
  16. package/src/components/Header.tsx +3 -0
  17. package/src/components/HitsPerPage.tsx +55 -18
  18. package/src/components/ImagePreview.tsx +31 -1
  19. package/src/components/ImageUpload.tsx +14 -3
  20. package/src/components/Inquiry/InquiryModal.tsx +3 -0
  21. package/src/components/LogoutModal.tsx +2 -0
  22. package/src/components/Pagination.tsx +33 -6
  23. package/src/components/PostFilter/PostFilter.tsx +8 -1
  24. package/src/components/PostFilter/PostFilterComponent.tsx +8 -22
  25. package/src/components/PostFilter/PostFilterFindApi.test.tsx +249 -0
  26. package/src/components/PostFilter/PostFilterFindApi.tsx +128 -52
  27. package/src/components/PreFilter/PreFilter.tsx +10 -2
  28. package/src/components/Product/Product.tsx +60 -10
  29. package/src/components/Product/ProductDetailView.tsx +23 -1
  30. package/src/components/Product/ProductDetailViewModal.tsx +5 -1
  31. package/src/components/Product/ProductList.tsx +51 -16
  32. package/src/components/Rfq/RfqModal.tsx +3 -0
  33. package/src/components/ScopeSelect/ScopeSelectForm.tsx +514 -0
  34. package/src/components/ScopeSelect/ScopeSelectImageActions.tsx +62 -0
  35. package/src/components/ScopeSelect/ScopeSelectImagePreview.tsx +141 -0
  36. package/src/components/ScopeSelect/ScopeSelectInfo.tsx +68 -0
  37. package/src/components/ScopeSelect/ScopeSelectInput.tsx +123 -0
  38. package/src/components/ScopeSelect/ScopeSelectModal.tsx +310 -0
  39. package/src/components/ScopeSelect/identifyTypePlateImage.test.ts +129 -0
  40. package/src/components/ScopeSelect/identifyTypePlateImage.ts +83 -0
  41. package/src/components/ScopeSelect/parseScopeIdResponse.test.ts +103 -0
  42. package/src/components/ScopeSelect/parseScopeIdResponse.ts +72 -0
  43. package/src/components/ScopeSelect/scopeSelectHelpers.ts +54 -0
  44. package/src/components/SidePanel.tsx +43 -0
  45. package/src/components/TextSearch.tsx +80 -14
  46. package/src/components/Tooltip/TooltipComponent.tsx +12 -4
  47. package/src/components/TypePlateUpload.tsx +132 -0
  48. package/src/hooks/useEffectiveGroundingResults.ts +8 -6
  49. package/src/hooks/useGoodResultsChat.ts +8 -22
  50. package/src/hooks/useImageSearch.ts +325 -20
  51. package/src/hooks/useMachinePartsListingPagination.ts +95 -0
  52. package/src/hooks/useResultEvaluator.ts +43 -19
  53. package/src/index.css +1 -1
  54. package/src/index.tsx +4 -0
  55. package/src/layouts/AppLayout.tsx +95 -9
  56. package/src/pages/Home.tsx +60 -14
  57. package/src/pages/Logout.tsx +2 -0
  58. package/src/pages/Result.tsx +206 -20
  59. package/src/providers/AuthProvider.tsx +5 -0
  60. package/src/services/Feedback.ts +224 -35
  61. package/src/services/feedbackSubtypes.ts +63 -0
  62. package/src/services/filter.ts +4 -3
  63. package/src/services/initialMachinePartsText.payload.test.ts +61 -0
  64. package/src/services/initialMachinePartsText.test.ts +33 -0
  65. package/src/services/initialMachinePartsText.ts +11 -0
  66. package/src/services/machinePartsPage.test.ts +74 -0
  67. package/src/services/machinePartsPage.ts +120 -0
  68. package/src/services/normalizeChatResponse.ts +26 -0
  69. package/src/services/scopeLookup.test.ts +67 -0
  70. package/src/services/scopeLookup.ts +39 -0
  71. package/src/services/visualSearch.listingOptions.test.ts +79 -0
  72. package/src/services/visualSearch.ts +76 -7
  73. package/src/services/vizo.ts +17 -5
  74. package/src/stores/machinePartsListing/machinePartsListingStore.ts +35 -0
  75. package/src/stores/request/filter/filter.initialState.ts +1 -0
  76. package/src/stores/request/filter/filter.slice.ts +2 -0
  77. package/src/stores/result/prodcuts/products.initialState.ts +1 -0
  78. package/src/stores/result/prodcuts/products.slice.ts +4 -0
  79. package/src/stores/scope/scopeStore.ts +67 -4
  80. package/src/stores/types.ts +19 -14
  81. package/src/translations.ts +213 -0
  82. package/src/types.ts +40 -0
  83. package/src/utils/derivePostFilterFacets.ts +68 -0
  84. package/src/utils/fetchProductImage.ts +1 -0
  85. package/src/utils/normalizeChatAssistantResponse.ts +227 -0
  86. package/src/utils/nyrisApiKey.ts +115 -0
  87. package/src/utils/productCardImageUrl.test.ts +41 -0
  88. package/src/utils/productCardImageUrl.ts +49 -0
  89. package/src/utils/textSearchFilter.test.ts +41 -0
  90. package/src/utils/textSearchFilter.ts +13 -6
  91. package/build/static/css/main.7c7f5447.css +0 -4
  92. package/build/static/css/main.7c7f5447.css.map +0 -1
  93. package/build/static/js/main.9e7fb38a.js +0 -3
  94. /package/build/static/js/{main.9e7fb38a.js.LICENSE.txt → main.2339271b.js.LICENSE.txt} +0 -0
@@ -13,6 +13,10 @@ import { twMerge } from 'tailwind-merge';
13
13
  import NoImage from '../../common/assets/images/no-image.svg';
14
14
  import ProductDetailViewModal from './ProductDetailViewModal';
15
15
  import Tooltip from 'components/Tooltip/TooltipComponent';
16
+ import { trackClick, trackConversion } from 'services/Feedback';
17
+ import { CLICK_SUBTYPE, CONVERSION_SUBTYPE } from 'services/feedbackSubtypes';
18
+ import useResultStore from 'stores/result/resultStore';
19
+ import { productCardImageUrl } from 'utils/productCardImageUrl';
16
20
 
17
21
  interface Props {
18
22
  dataItem: any;
@@ -35,8 +39,15 @@ function Product(props: Props) {
35
39
  onSearchImage,
36
40
  handlerFeedback,
37
41
  main_image_link,
42
+ indexItem,
38
43
  } = props;
44
+ const productRef = { sku: dataItem?.sku, position: indexItem };
39
45
  const [urlImage, setUrlImage] = useState<string>('');
46
+ const [showOriginalImage, setShowOriginalImage] = useState(false);
47
+ const cardImageUrl =
48
+ typeof main_image_link === 'string' && main_image_link
49
+ ? productCardImageUrl(main_image_link)
50
+ : '';
40
51
  const settings = window.settings;
41
52
  const [copiedKey, setCopiedKey] = useState<'main' | 'secondary' | null>(null);
42
53
  const copyTimeoutRef = useRef<number | undefined>(undefined);
@@ -46,8 +57,9 @@ function Product(props: Props) {
46
57
  >();
47
58
 
48
59
  useEffect(() => {
49
- if (main_image_link) {
50
- handlerCheckUrlImage(main_image_link);
60
+ setShowOriginalImage(false);
61
+ if (typeof main_image_link === 'string' && main_image_link) {
62
+ handlerCheckUrlImage(productCardImageUrl(main_image_link));
51
63
  }
52
64
  }, [main_image_link]);
53
65
 
@@ -81,17 +93,26 @@ function Product(props: Props) {
81
93
  };
82
94
 
83
95
  const handlerToggleModal = (_item?: any) => {
96
+ trackClick(CLICK_SUBTYPE.RESULT_CARD, productRef);
84
97
  setOpenDetailedView('image');
85
98
  };
86
99
 
87
- const onAddToCart = (part: any): void => {
100
+ const onAddToCart = (part: any, position: number = indexItem): void => {
88
101
  // Get current cart from sessionStorage
89
102
  const cart = JSON.parse(sessionStorage.getItem('cart') || '[]');
90
103
  // Find if part already exists in cart
91
104
  const partIndex = cart.findIndex((p: any) => p.sku === part.sku);
92
105
  if (partIndex === -1) {
93
- // Add new part with quantity 1
94
- cart.push({ ...part, quantity: 1 });
106
+ // Add new part with quantity 1; remember which search it came from so
107
+ // cart conversions can be tied back to the originating request.
108
+ const { requestId, sessionId } = useResultStore.getState();
109
+ cart.push({
110
+ ...part,
111
+ quantity: 1,
112
+ feedbackRequestId: requestId,
113
+ feedbackSessionId: sessionId,
114
+ feedbackPosition: position,
115
+ });
95
116
  } else {
96
117
  // Increment quantity
97
118
  cart[partIndex] = {
@@ -102,10 +123,16 @@ function Product(props: Props) {
102
123
  sessionStorage.setItem('cart', JSON.stringify(cart));
103
124
  window.dispatchEvent(new Event('cart-updated'));
104
125
  ToastHelper.success('Product added');
126
+ trackConversion(CONVERSION_SUBTYPE.ADD_TO_CART, {
127
+ skus: [part?.sku],
128
+ positions: [position],
129
+ quantity: 1,
130
+ });
105
131
  };
106
132
 
107
133
  const handleCopy = (value: string, key: 'main' | 'secondary') => {
108
134
  navigator.clipboard.writeText(value);
135
+ trackClick(CLICK_SUBTYPE.COPY_DETAILS, productRef);
109
136
  setCopiedKey(key);
110
137
  if (copyTimeoutRef.current) {
111
138
  window.clearTimeout(copyTimeoutRef.current);
@@ -124,6 +151,7 @@ function Product(props: Props) {
124
151
  openDetailedView={openDetailedView}
125
152
  setOpenDetailedView={setOpenDetailedView}
126
153
  onAddToCart={onAddToCart}
154
+ indexItem={indexItem}
127
155
  />
128
156
  <div
129
157
  className={twMerge(
@@ -141,6 +169,7 @@ function Product(props: Props) {
141
169
  className="box-icon-modal"
142
170
  onClick={() => {
143
171
  if (urlImage.length > 1) {
172
+ trackClick(CLICK_SUBTYPE.SIMILAR_PART_SEARCH, productRef);
144
173
  onSearchImage(main_image_link);
145
174
  }
146
175
  }}
@@ -157,6 +186,7 @@ function Product(props: Props) {
157
186
  <div
158
187
  className="box-icon-modal-3d"
159
188
  onClick={() => {
189
+ trackClick(CLICK_SUBTYPE.EXPAND_DETAILS, productRef);
160
190
  setOpenDetailedView('3d');
161
191
  }}
162
192
  >
@@ -189,7 +219,11 @@ function Product(props: Props) {
189
219
  >
190
220
  {main_image_link && (
191
221
  <img
192
- src={main_image_link}
222
+ src={
223
+ showOriginalImage || !cardImageUrl
224
+ ? main_image_link
225
+ : cardImageUrl
226
+ }
193
227
  key={main_image_link}
194
228
  alt=""
195
229
  className="img-style product-image"
@@ -198,6 +232,13 @@ function Product(props: Props) {
198
232
  height: '100%',
199
233
  objectFit: 'contain',
200
234
  }}
235
+ onError={() => {
236
+ if (showOriginalImage || cardImageUrl === main_image_link) {
237
+ return;
238
+ }
239
+ setShowOriginalImage(true);
240
+ handlerCheckUrlImage(main_image_link);
241
+ }}
201
242
  />
202
243
  )}
203
244
 
@@ -265,7 +306,10 @@ function Product(props: Props) {
265
306
  }}
266
307
  onClick={() => {
267
308
  if (settings.CTAButton?.CTALinkField) {
268
- // feedbackConversionEpic(state, indexItem, dataItem.sku);
309
+ trackConversion(undefined, {
310
+ skus: [dataItem?.sku],
311
+ positions: [indexItem],
312
+ });
269
313
  window.open(
270
314
  `${get(dataItem, settings.CTAButton?.CTALinkField)}`,
271
315
  '_blank',
@@ -533,7 +577,10 @@ function Product(props: Props) {
533
577
  }}
534
578
  onClick={() => {
535
579
  if (settings.secondaryCTAButton?.secondaryCTALinkField) {
536
- // feedbackConversionEpic(state, indexItem, dataItem.sku);
580
+ trackConversion(undefined, {
581
+ skus: [dataItem?.sku],
582
+ positions: [indexItem],
583
+ });
537
584
  window.open(
538
585
  `${get(
539
586
  dataItem,
@@ -616,7 +663,10 @@ function Product(props: Props) {
616
663
  }}
617
664
  onClick={() => {
618
665
  if (settings.CTAButton?.CTALinkField) {
619
- // feedbackConversionEpic(state, indexItem, dataItem.sku);
666
+ trackConversion(undefined, {
667
+ skus: [dataItem?.sku],
668
+ positions: [indexItem],
669
+ });
620
670
  window.open(
621
671
  `${get(dataItem, settings.CTAButton?.CTALinkField)}`,
622
672
  '_blank',
@@ -700,7 +750,7 @@ function Product(props: Props) {
700
750
  cursor: 'pointer',
701
751
  }}
702
752
  onClick={() => {
703
- onAddToCart(dataItem);
753
+ onAddToCart(dataItem, indexItem);
704
754
  }}
705
755
  >
706
756
  <div
@@ -20,6 +20,8 @@ import {
20
20
  AccordionTrigger,
21
21
  } from 'components/Accordion';
22
22
  import Tooltip from 'components/Tooltip/TooltipComponent';
23
+ import { trackClick, trackConversion } from 'services/Feedback';
24
+ import { CLICK_SUBTYPE } from 'services/feedbackSubtypes';
23
25
 
24
26
  interface Props {
25
27
  dataItem?: any;
@@ -29,6 +31,8 @@ interface Props {
29
31
  show3dView?: boolean;
30
32
  onSearchImage?: any;
31
33
  onAddToCart?: any;
34
+ /** Zero-based position of the product in the rendered result list. */
35
+ indexItem?: number;
32
36
  }
33
37
 
34
38
  function ProductDetailView(props: Props) {
@@ -38,8 +42,10 @@ function ProductDetailView(props: Props) {
38
42
  show3dView = false,
39
43
  onSearchImage,
40
44
  onAddToCart,
45
+ indexItem,
41
46
  } = props;
42
47
  const { sku } = dataItem;
48
+ const productRef = { sku, position: indexItem };
43
49
  const isMobile = useMediaQuery({ query: '(max-width: 776px)' });
44
50
  const { settings } = window;
45
51
 
@@ -76,6 +82,7 @@ function ProductDetailView(props: Props) {
76
82
  }, []);
77
83
  const handleCopy = (value: string, key: 'main' | 'secondary') => {
78
84
  navigator.clipboard.writeText(value);
85
+ trackClick(CLICK_SUBTYPE.COPY_DETAILS, productRef);
79
86
  setCopiedKey(key);
80
87
  if (copyTimeoutRef.current) {
81
88
  window.clearTimeout(copyTimeoutRef.current);
@@ -151,6 +158,7 @@ function ProductDetailView(props: Props) {
151
158
  }`}
152
159
  onClick={() => {
153
160
  if (urlImage.length > 1) {
161
+ trackClick(CLICK_SUBTYPE.SIMILAR_PART_SEARCH, productRef);
154
162
  onSearchImage(urlImage);
155
163
  handleClose?.();
156
164
  return;
@@ -176,6 +184,7 @@ function ProductDetailView(props: Props) {
176
184
  <div
177
185
  className="bg-[#E9E9EC] w-8 h-8 rounded-full flex justify-center items-center cursor-pointer"
178
186
  onClick={() => {
187
+ trackClick(CLICK_SUBTYPE.EXPAND_DETAILS, productRef);
179
188
  setIs3dView(true);
180
189
  }}
181
190
  >
@@ -356,6 +365,10 @@ function ProductDetailView(props: Props) {
356
365
  if (
357
366
  settings.secondaryCTAButton?.secondaryCTALinkField
358
367
  ) {
368
+ trackConversion(undefined, {
369
+ skus: [sku],
370
+ positions: [indexItem],
371
+ });
359
372
  window.open(
360
373
  `${get(
361
374
  dataItem,
@@ -406,6 +419,10 @@ function ProductDetailView(props: Props) {
406
419
  className="flex justify-between items-center w-full px-3 min-h-8 cursor-pointer"
407
420
  onClick={() => {
408
421
  if (settings.CTAButton?.CTALinkField) {
422
+ trackConversion(undefined, {
423
+ skus: [sku],
424
+ positions: [indexItem],
425
+ });
409
426
  window.open(
410
427
  `${get(
411
428
  dataItem,
@@ -468,7 +485,7 @@ function ProductDetailView(props: Props) {
468
485
  <div
469
486
  className="flex justify-between items-center w-full px-3 min-h-8 cursor-pointer"
470
487
  onClick={() => {
471
- onAddToCart(dataItem);
488
+ onAddToCart(dataItem, indexItem);
472
489
  }}
473
490
  >
474
491
  <div className="text-f16 fw-600 max-line-1 text-white pr-1.5">
@@ -495,6 +512,11 @@ function ProductDetailView(props: Props) {
495
512
  collapsible
496
513
  className="w-full"
497
514
  defaultValue="view-details"
515
+ onValueChange={value => {
516
+ if (value) {
517
+ trackClick(CLICK_SUBTYPE.EXPAND_DETAILS, productRef);
518
+ }
519
+ }}
498
520
  >
499
521
  <AccordionItem value="view-details">
500
522
  <AccordionTrigger className="w-full button-hover bg-[#F3F3F5] text-[#2b2c46] flex justify-between mt-3 px-4 text-base normal-case">
@@ -9,13 +9,16 @@ function ProductDetailViewModal({
9
9
  handlerFeedback,
10
10
  onSearchImage,
11
11
  onAddToCart,
12
+ indexItem,
12
13
  }: {
13
14
  openDetailedView: '3d' | 'image' | undefined;
14
15
  setOpenDetailedView: (value: '3d' | 'image' | undefined) => void;
15
16
  dataItem: any;
16
17
  handlerFeedback: (value: string) => void;
17
18
  onSearchImage: (url: string) => void;
18
- onAddToCart: (item: any) => void;
19
+ onAddToCart: (item: any, position?: number) => void;
20
+ /** Zero-based position of the product in the rendered result list. */
21
+ indexItem?: number;
19
22
  }) {
20
23
  useEffect(() => {
21
24
  if (openDetailedView === '3d' || openDetailedView === 'image') {
@@ -64,6 +67,7 @@ function ProductDetailViewModal({
64
67
  onSearchImage(url);
65
68
  }}
66
69
  onAddToCart={onAddToCart}
70
+ indexItem={indexItem}
67
71
  />
68
72
  </DialogContent>
69
73
  </Dialog>
@@ -4,6 +4,7 @@ import Product from './Product';
4
4
  import { useImageSearch } from 'hooks/useImageSearch';
5
5
  import useRequestStore from 'stores/request/requestStore';
6
6
  import useUiStore from 'stores/ui/uiStore';
7
+ import useScopeStore from 'stores/scope/scopeStore';
7
8
  import { useTranslation } from 'react-i18next';
8
9
  import { twMerge } from 'tailwind-merge';
9
10
  import { filterProducts } from 'utils/specificationFilter';
@@ -12,9 +13,13 @@ import { useEffectiveGroundingResults } from 'hooks/useEffectiveGroundingResults
12
13
 
13
14
  interface Props {
14
15
  sendFeedBackAction?: any;
16
+ onRenderedCountChange?: (count: number) => void;
15
17
  }
16
18
 
17
- function ProductList({ sendFeedBackAction }: Props): JSX.Element {
19
+ function ProductList({
20
+ sendFeedBackAction,
21
+ onRenderedCountChange,
22
+ }: Props): JSX.Element {
18
23
  const settings = window.settings;
19
24
  const { singleImageSearch } = useImageSearch();
20
25
  const { t } = useTranslation();
@@ -49,7 +54,15 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
49
54
  const postFilterSelections = useRequestStore(
50
55
  state => state.postFilterSelections,
51
56
  );
52
-
57
+ const useApiPostFilters =
58
+ !settings.algolia?.enabled && settings.postFilterUseApi === true;
59
+
60
+ const scopeId = useScopeStore(state => state.scopeId);
61
+ const scopeItemKeyCount = useScopeStore(state => state.itemKeys.length);
62
+ const hasMachineScope =
63
+ Boolean(window.settings.scopes?.scopeID) &&
64
+ Boolean(scopeId) &&
65
+ scopeItemKeyCount > 0;
53
66
  const textFilteredProducts = useMemo(() => {
54
67
  if (!requestImages?.length) {
55
68
  return productsFromFindApi;
@@ -63,7 +76,7 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
63
76
  }, [productsFromFindApi, query, requestImages]);
64
77
 
65
78
  const postFilteredProducts = useMemo(() => {
66
- if (!window.settings.algolia?.enabled && window.settings.postFilterOption) {
79
+ if (useApiPostFilters) {
67
80
  return textFilteredProducts;
68
81
  }
69
82
 
@@ -87,7 +100,7 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
87
100
  return selectedValues.some(value => productValues.includes(value));
88
101
  });
89
102
  });
90
- }, [postFilterSelections, textFilteredProducts]);
103
+ }, [postFilterSelections, textFilteredProducts, useApiPostFilters]);
91
104
 
92
105
  const getUrlToCanvasFile = async (url: string) => {
93
106
  setQuery('');
@@ -97,6 +110,7 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
97
110
  image: url,
98
111
  settings,
99
112
  showFeedback: true,
113
+ newSearch: true,
100
114
  compress: false,
101
115
  clearPostFilter: true,
102
116
  }).then(() => {});
@@ -107,14 +121,18 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
107
121
  const filter = Object.values(specificationFilter || {})[0];
108
122
  const baseFindApiProducts = postFilteredProducts;
109
123
 
124
+ const resultSource = hasMachineScope
125
+ ? baseFindApiProducts
126
+ : isAlgoliaEnabled
127
+ ? productsFromAlgolia
128
+ : baseFindApiProducts;
129
+
110
130
  if (filter) {
111
- return filterProducts(
112
- filter,
113
- isAlgoliaEnabled ? productsFromAlgolia : baseFindApiProducts,
114
- );
131
+ return filterProducts(filter, resultSource);
115
132
  }
116
133
 
117
- if (!isAlgoliaEnabled) {
134
+ // Scoped results always come from Find API. Algolia cannot apply a machine scope.
135
+ if (hasMachineScope || !isAlgoliaEnabled) {
118
136
  return baseFindApiProducts;
119
137
  }
120
138
 
@@ -129,6 +147,7 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
129
147
  isAlgoliaLoading,
130
148
  query,
131
149
  postFilteredProducts,
150
+ hasMachineScope,
132
151
  ]);
133
152
 
134
153
  useEffect(() => {
@@ -150,14 +169,24 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
150
169
  return products;
151
170
  }, [effectiveGroundingResults, products]);
152
171
 
153
- const renderItem = useMemo(() => {
154
- const productsToRender: any[] =
155
- groundedFilteredProducts &&
172
+ const productsToRender: any[] = useMemo(() => {
173
+ return groundedFilteredProducts &&
156
174
  groundedFilteredProducts.length > 0 &&
157
175
  showingGroundingFilterResult
158
- ? groundedFilteredProducts
159
- : products;
176
+ ? groundedFilteredProducts
177
+ : products;
178
+ }, [groundedFilteredProducts, showingGroundingFilterResult, products]);
160
179
 
180
+ const isZeroState =
181
+ products?.length === 0 && !isAlgoliaLoading && !isFindApiLoading;
182
+
183
+ const actualRenderedCount = isZeroState ? 0 : productsToRender.length;
184
+
185
+ useEffect(() => {
186
+ onRenderedCountChange?.(actualRenderedCount);
187
+ }, [actualRenderedCount, onRenderedCountChange]);
188
+
189
+ const renderItem = useMemo(() => {
161
190
  return (
162
191
  <div className={twMerge(['contents'])}>
163
192
  {productsToRender?.map((product: any, i: number) => {
@@ -202,6 +231,7 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
202
231
  groundedFilteredProducts,
203
232
  showingGroundingFilterResult,
204
233
  isAiModeOpen,
234
+ productsToRender,
205
235
  ]);
206
236
 
207
237
  if (
@@ -209,7 +239,8 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
209
239
  !isAlgoliaLoading &&
210
240
  !isFindApiLoading &&
211
241
  !query &&
212
- !requestImages[0]
242
+ !requestImages[0] &&
243
+ !hasMachineScope
213
244
  ) {
214
245
  return (
215
246
  <div className="flex justify-center items-center h-full col-span-full">
@@ -220,7 +251,11 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
220
251
  );
221
252
  }
222
253
 
223
- if (products?.length === 0 && !isAlgoliaLoading && !isFindApiLoading) {
254
+ if (
255
+ products?.length === 0 &&
256
+ !isAlgoliaLoading &&
257
+ !isFindApiLoading
258
+ ) {
224
259
  return (
225
260
  <div className="flex justify-center items-center h-full col-span-full">
226
261
  <div className="text-center text-[#AAABB5]">
@@ -9,6 +9,8 @@ import { Dialog, DialogContent } from 'components/Modal/Dialog';
9
9
  import { getCroppedCanvas } from 'utils/misc';
10
10
  import { ToastHelper } from 'helpers/ToastHelper';
11
11
  import useRequestStore from 'stores/request/requestStore';
12
+ import { trackClick } from 'services/Feedback';
13
+ import { CLICK_SUBTYPE } from 'services/feedbackSubtypes';
12
14
 
13
15
  import { useTranslation } from 'react-i18next';
14
16
  interface Props {
@@ -77,6 +79,7 @@ export default function RfqModal({
77
79
  });
78
80
  setRfqStatus('sent');
79
81
  ToastHelper.success(t('Request sent successfully'));
82
+ trackClick(CLICK_SUBTYPE.RAISE_INQUIRY);
80
83
  } catch (error) {
81
84
  setRfqStatus('inactive');
82
85