@nyris/nyris-webapp 0.3.98 → 0.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.
Files changed (97) hide show
  1. package/build/asset-manifest.json +7 -6
  2. package/build/index.html +1 -1
  3. package/build/js/settings.example.js +42 -0
  4. package/build/static/css/main.a7ff7a7f.css +4 -0
  5. package/build/static/css/main.a7ff7a7f.css.map +1 -0
  6. package/build/static/js/main.916c157c.js +3 -0
  7. package/build/static/js/{main.93fdbd6e.js.map → main.916c157c.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 +42 -0
  11. package/src/common/assets/images/plate_info.png +0 -0
  12. package/src/components/Cart.tsx +41 -0
  13. package/src/components/ChatAssistant/ChatAssistant.test.tsx +213 -0
  14. package/src/components/ChatAssistant/ChatAssistant.tsx +18 -1
  15. package/src/components/ChatAssistant/MobileChatAssistant.tsx +18 -1
  16. package/src/components/ChatAssistant/OptionChip.tsx +23 -1
  17. package/src/components/ChatAssistant/useChatAssistantLogic.ts +35 -14
  18. package/src/components/GoBackButton.tsx +29 -2
  19. package/src/components/Header.tsx +3 -0
  20. package/src/components/HitsPerPage.tsx +55 -18
  21. package/src/components/ImagePreview.tsx +31 -1
  22. package/src/components/ImageUpload.tsx +12 -1
  23. package/src/components/Inquiry/InquiryModal.tsx +61 -26
  24. package/src/components/Pagination.tsx +33 -6
  25. package/src/components/PostFilter/PostFilter.tsx +8 -1
  26. package/src/components/PostFilter/PostFilterFindApi.test.tsx +249 -0
  27. package/src/components/PostFilter/PostFilterFindApi.tsx +128 -52
  28. package/src/components/PreFilter/PreFilter.tsx +10 -2
  29. package/src/components/Product/Product.tsx +60 -10
  30. package/src/components/Product/ProductDetailView.tsx +23 -1
  31. package/src/components/Product/ProductDetailViewModal.tsx +5 -1
  32. package/src/components/Product/ProductList.tsx +51 -18
  33. package/src/components/Rfq/RfqModal.tsx +3 -0
  34. package/src/components/ScopeSelect/ScopeSelectForm.tsx +514 -0
  35. package/src/components/ScopeSelect/ScopeSelectImageActions.tsx +62 -0
  36. package/src/components/ScopeSelect/ScopeSelectImagePreview.tsx +141 -0
  37. package/src/components/ScopeSelect/ScopeSelectInfo.tsx +68 -0
  38. package/src/components/ScopeSelect/ScopeSelectInput.tsx +123 -0
  39. package/src/components/ScopeSelect/ScopeSelectModal.tsx +310 -0
  40. package/src/components/ScopeSelect/identifyTypePlateImage.test.ts +129 -0
  41. package/src/components/ScopeSelect/identifyTypePlateImage.ts +83 -0
  42. package/src/components/ScopeSelect/parseScopeIdResponse.test.ts +103 -0
  43. package/src/components/ScopeSelect/parseScopeIdResponse.ts +72 -0
  44. package/src/components/ScopeSelect/scopeSelectHelpers.ts +54 -0
  45. package/src/components/SidePanel.tsx +33 -0
  46. package/src/components/TextSearch.tsx +77 -11
  47. package/src/components/Tooltip/TooltipComponent.tsx +12 -4
  48. package/src/components/TypePlateUpload.tsx +132 -0
  49. package/src/hooks/chatProductMetadata.test.ts +307 -0
  50. package/src/hooks/useBadResultsRecovery.ts +26 -49
  51. package/src/hooks/useEffectiveGroundingResults.ts +8 -6
  52. package/src/hooks/useGoodResultsChat.ts +28 -39
  53. package/src/hooks/useImageSearch.ts +290 -12
  54. package/src/hooks/useMachinePartsListingPagination.ts +95 -0
  55. package/src/hooks/useResultEvaluator.ts +63 -73
  56. package/src/index.css +1 -1
  57. package/src/layouts/AppLayout.tsx +43 -3
  58. package/src/pages/Home.tsx +60 -14
  59. package/src/pages/Result.tsx +194 -14
  60. package/src/services/Feedback.ts +225 -34
  61. package/src/services/feedbackSubtypes.ts +63 -0
  62. package/src/services/filter.ts +1 -1
  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 +71 -3
  73. package/src/services/vizo.ts +4 -3
  74. package/src/stores/chat/conversationStore.test.ts +133 -0
  75. package/src/stores/chat/conversationStore.ts +55 -14
  76. package/src/stores/machinePartsListing/machinePartsListingStore.ts +35 -0
  77. package/src/stores/request/filter/filter.initialState.ts +1 -0
  78. package/src/stores/request/filter/filter.slice.ts +2 -0
  79. package/src/stores/result/prodcuts/products.initialState.ts +1 -0
  80. package/src/stores/result/prodcuts/products.slice.ts +4 -0
  81. package/src/stores/scope/scopeStore.ts +67 -4
  82. package/src/stores/types.ts +19 -14
  83. package/src/translations.ts +213 -0
  84. package/src/types.ts +41 -0
  85. package/src/utils/chatProductMetadata.test.ts +130 -0
  86. package/src/utils/chatProductMetadata.ts +93 -0
  87. package/src/utils/compressImage.ts +3 -3
  88. package/src/utils/fetchProductImage.ts +1 -0
  89. package/src/utils/normalizeChatAssistantResponse.ts +227 -0
  90. package/src/utils/productCardImageUrl.test.ts +41 -0
  91. package/src/utils/productCardImageUrl.ts +49 -0
  92. package/src/utils/textSearchFilter.test.ts +41 -0
  93. package/src/utils/textSearchFilter.ts +13 -6
  94. package/build/static/css/main.81216c5d.css +0 -4
  95. package/build/static/css/main.81216c5d.css.map +0 -1
  96. package/build/static/js/main.93fdbd6e.js +0 -3
  97. /package/build/static/js/{main.93fdbd6e.js.LICENSE.txt → main.916c157c.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,8 +76,7 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
63
76
  }, [productsFromFindApi, query, requestImages]);
64
77
 
65
78
  const postFilteredProducts = useMemo(() => {
66
- // Find-API post-filters re-query when Algolia is off; do not also client-filter.
67
- if (!window.settings.algolia?.enabled && window.settings.postFilterOption) {
79
+ if (useApiPostFilters) {
68
80
  return textFilteredProducts;
69
81
  }
70
82
 
@@ -88,7 +100,7 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
88
100
  return selectedValues.some(value => productValues.includes(value));
89
101
  });
90
102
  });
91
- }, [postFilterSelections, textFilteredProducts]);
103
+ }, [postFilterSelections, textFilteredProducts, useApiPostFilters]);
92
104
 
93
105
  const getUrlToCanvasFile = async (url: string) => {
94
106
  setQuery('');
@@ -98,6 +110,7 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
98
110
  image: url,
99
111
  settings,
100
112
  showFeedback: true,
113
+ newSearch: true,
101
114
  compress: false,
102
115
  clearPostFilter: true,
103
116
  }).then(() => {});
@@ -108,15 +121,18 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
108
121
  const filter = Object.values(specificationFilter || {})[0];
109
122
  const baseFindApiProducts = postFilteredProducts;
110
123
 
124
+ const resultSource = hasMachineScope
125
+ ? baseFindApiProducts
126
+ : isAlgoliaEnabled
127
+ ? productsFromAlgolia
128
+ : baseFindApiProducts;
129
+
111
130
  if (filter) {
112
- return filterProducts(
113
- filter,
114
- isAlgoliaEnabled ? productsFromAlgolia : baseFindApiProducts,
115
- );
131
+ return filterProducts(filter, resultSource);
116
132
  }
117
133
 
118
- // Product source follows search backend (Algolia vs Find API), not postFilterOption.
119
- if (!isAlgoliaEnabled) {
134
+ // Scoped results always come from Find API. Algolia cannot apply a machine scope.
135
+ if (hasMachineScope || !isAlgoliaEnabled) {
120
136
  return baseFindApiProducts;
121
137
  }
122
138
 
@@ -131,6 +147,7 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
131
147
  isAlgoliaLoading,
132
148
  query,
133
149
  postFilteredProducts,
150
+ hasMachineScope,
134
151
  ]);
135
152
 
136
153
  useEffect(() => {
@@ -152,14 +169,24 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
152
169
  return products;
153
170
  }, [effectiveGroundingResults, products]);
154
171
 
155
- const renderItem = useMemo(() => {
156
- const productsToRender: any[] =
157
- groundedFilteredProducts &&
172
+ const productsToRender: any[] = useMemo(() => {
173
+ return groundedFilteredProducts &&
158
174
  groundedFilteredProducts.length > 0 &&
159
175
  showingGroundingFilterResult
160
- ? groundedFilteredProducts
161
- : products;
176
+ ? groundedFilteredProducts
177
+ : products;
178
+ }, [groundedFilteredProducts, showingGroundingFilterResult, products]);
162
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(() => {
163
190
  return (
164
191
  <div className={twMerge(['contents'])}>
165
192
  {productsToRender?.map((product: any, i: number) => {
@@ -204,6 +231,7 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
204
231
  groundedFilteredProducts,
205
232
  showingGroundingFilterResult,
206
233
  isAiModeOpen,
234
+ productsToRender,
207
235
  ]);
208
236
 
209
237
  if (
@@ -211,7 +239,8 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
211
239
  !isAlgoliaLoading &&
212
240
  !isFindApiLoading &&
213
241
  !query &&
214
- !requestImages[0]
242
+ !requestImages[0] &&
243
+ !hasMachineScope
215
244
  ) {
216
245
  return (
217
246
  <div className="flex justify-center items-center h-full col-span-full">
@@ -222,7 +251,11 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
222
251
  );
223
252
  }
224
253
 
225
- if (products?.length === 0 && !isAlgoliaLoading && !isFindApiLoading) {
254
+ if (
255
+ products?.length === 0 &&
256
+ !isAlgoliaLoading &&
257
+ !isFindApiLoading
258
+ ) {
226
259
  return (
227
260
  <div className="flex justify-center items-center h-full col-span-full">
228
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