@nyris/nyris-webapp 0.3.88 → 0.3.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/_headers +2 -0
- package/build/asset-manifest.json +6 -6
- package/build/index.html +1 -1
- package/build/js/settings.example.js +17 -0
- package/build/static/css/main.734b52e1.css +4 -0
- package/build/static/css/main.734b52e1.css.map +1 -0
- package/build/static/js/main.cede3ae1.js +3 -0
- package/build/static/js/{main.e861b336.js.map → main.cede3ae1.js.map} +1 -1
- package/package.json +3 -3
- package/public/_headers +2 -0
- package/public/index.html +1 -1
- package/public/js/settings.example.js +17 -0
- package/src/App.tsx +5 -3
- package/src/components/Cart.tsx +321 -0
- package/src/components/CustomCameraDrawer.tsx +4 -22
- package/src/components/DragDropFile.tsx +57 -38
- package/src/components/ExperienceVisualSearch/ExperienceVisualSearch.tsx +6 -1
- package/src/components/ExperienceVisualSearch/ExperienceVisualSearchTrigger.tsx +2 -2
- package/src/components/GroundingSpecs.tsx +47 -0
- package/src/components/Header.tsx +94 -93
- package/src/components/HitsPerPage.tsx +4 -2
- package/src/components/ImagePreview.tsx +64 -31
- package/src/components/ImageUpload.tsx +247 -0
- package/src/components/ItemSpecification.tsx +164 -0
- package/src/components/MatchNotificationBanner.tsx +165 -0
- package/src/components/PostFilter/PostFilter.tsx +22 -1
- package/src/components/PostFilter/PostFilterComponent.tsx +59 -26
- package/src/components/PostFilter/PostFilterFindApi.tsx +242 -0
- package/src/components/PoweredBy.tsx +16 -0
- package/src/components/PreFilter/PreFilter.tsx +77 -54
- package/src/components/Product/Product.tsx +186 -28
- package/src/components/Product/ProductAttribute.tsx +2 -2
- package/src/components/Product/ProductDetailView.tsx +123 -18
- package/src/components/Product/ProductDetailViewModal.tsx +3 -0
- package/src/components/Product/ProductList.tsx +78 -8
- package/src/components/SidePanel.tsx +212 -120
- package/src/components/TextSearch.tsx +82 -203
- package/src/components/Toaster.tsx +34 -15
- package/src/helpers/ToastHelper.ts +6 -2
- package/src/hooks/useCadSearch.ts +5 -0
- package/src/hooks/useImageSearch.ts +102 -13
- package/src/index.css +59 -0
- package/src/layouts/AppLayout.tsx +16 -14
- package/src/pages/Home.tsx +61 -13
- package/src/pages/Result.tsx +287 -295
- package/src/services/vizo.ts +161 -0
- package/src/stores/request/Misc/misc.initialstate.ts +1 -0
- package/src/stores/request/Misc/misc.slice.ts +1 -0
- package/src/stores/request/filter/filter.initialState.ts +3 -0
- package/src/stores/request/filter/filter.slice.ts +23 -0
- package/src/stores/result/prodcuts/products.initialState.ts +4 -0
- package/src/stores/result/prodcuts/products.slice.ts +15 -0
- package/src/stores/types.ts +27 -1
- package/src/stores/ui/loading/loading.initialState.ts +1 -0
- package/src/stores/ui/loading/loading.slice.ts +4 -0
- package/src/stores/ui/sidePanel/sidePanel.initialState.ts +5 -0
- package/src/stores/ui/sidePanel/sidePanel.slice.ts +11 -0
- package/src/stores/ui/uiStore.ts +4 -1
- package/src/styles/Cart.scss +210 -0
- package/src/styles/common.scss +10 -0
- package/src/translations.ts +4 -4
- package/src/types.ts +11 -3
- package/src/utils/prepareImageList.ts +6 -5
- package/src/utils/textSearchFilter.ts +203 -0
- package/tailwind.config.js +1 -0
- package/build/static/css/main.ba1c7479.css +0 -4
- package/build/static/css/main.ba1c7479.css.map +0 -1
- package/build/static/js/main.e861b336.js +0 -3
- package/src/components/Footer.tsx +0 -21
- /package/build/static/js/{main.e861b336.js.LICENSE.txt → main.cede3ae1.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
// Utility: Convert HTMLCanvasElement to Blob
|
|
3
|
+
export function canvasToBlob(
|
|
4
|
+
canvas: HTMLCanvasElement,
|
|
5
|
+
type = 'image/jpeg',
|
|
6
|
+
quality = 0.95,
|
|
7
|
+
): Promise<Blob> {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
canvas.toBlob(
|
|
10
|
+
blob => {
|
|
11
|
+
if (blob) {
|
|
12
|
+
resolve(blob);
|
|
13
|
+
} else {
|
|
14
|
+
reject(new Error('Canvas toBlob failed'));
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
type,
|
|
18
|
+
quality,
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function buildProductEntry(p: any, index: number): string {
|
|
24
|
+
const title = p.Bezeichnung || p.title || p.name || '';
|
|
25
|
+
const desc =
|
|
26
|
+
p.VK_Text_Englisch ||
|
|
27
|
+
p.VK_Text_Deutsch ||
|
|
28
|
+
p.description ||
|
|
29
|
+
p.description_short ||
|
|
30
|
+
'';
|
|
31
|
+
const sku = p.sku || '';
|
|
32
|
+
const brand = p.brand || '';
|
|
33
|
+
|
|
34
|
+
// Include customIds metadata
|
|
35
|
+
const customIds = p.customIds || {};
|
|
36
|
+
const mpn = customIds.mpn || customIds.mpn_original || p.mpn || '';
|
|
37
|
+
const normalizedPN =
|
|
38
|
+
customIds['normalized PN'] || customIds['normalized_PN'] || '';
|
|
39
|
+
const normalizedModel =
|
|
40
|
+
customIds['normalized model'] || customIds['normalized_model'] || '';
|
|
41
|
+
const gtin = customIds.gtin || '';
|
|
42
|
+
|
|
43
|
+
const idParts: string[] = [];
|
|
44
|
+
if (mpn) idParts.push(`MPN: ${mpn}`);
|
|
45
|
+
if (normalizedPN && normalizedPN !== mpn)
|
|
46
|
+
idParts.push(`NormalizedPN: ${normalizedPN}`);
|
|
47
|
+
if (normalizedModel && normalizedModel !== normalizedPN)
|
|
48
|
+
idParts.push(`Model: ${normalizedModel}`);
|
|
49
|
+
if (gtin) idParts.push(`GTIN: ${gtin}`);
|
|
50
|
+
|
|
51
|
+
const idStr = idParts.length > 0 ? ` | IDs: ${idParts.join(', ')}` : '';
|
|
52
|
+
|
|
53
|
+
return `[${index}] SKU: ${sku} | Brand: ${brand} | ${title} | ${desc}${idStr}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Calls the nyris grounded search API with a File or HTMLCanvasElement.
|
|
58
|
+
* @param imageInput File or HTMLCanvasElement
|
|
59
|
+
* @param apiKey nyris API key
|
|
60
|
+
*/
|
|
61
|
+
export async function groundedSearch(
|
|
62
|
+
imageInput: File | HTMLCanvasElement,
|
|
63
|
+
apiKey: string,
|
|
64
|
+
): Promise<any> {
|
|
65
|
+
const formData = new FormData();
|
|
66
|
+
let imageBlobOrFile: File | Blob;
|
|
67
|
+
|
|
68
|
+
if (imageInput instanceof HTMLCanvasElement) {
|
|
69
|
+
imageBlobOrFile = await canvasToBlob(imageInput);
|
|
70
|
+
formData.append('image', imageBlobOrFile, 'canvas.jpg');
|
|
71
|
+
} else {
|
|
72
|
+
formData.append('image', imageInput);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const response = await axios.post(
|
|
77
|
+
'https://api.nyris.io/api/ai-analysis/grounded-search',
|
|
78
|
+
formData,
|
|
79
|
+
{
|
|
80
|
+
headers: {
|
|
81
|
+
'x-api-key': window.settings.aiApiKey || '',
|
|
82
|
+
// Let axios set Content-Type with boundary automatically
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
return response.data;
|
|
87
|
+
} catch (error: any) {
|
|
88
|
+
if (error.response) {
|
|
89
|
+
throw new Error(`API error: ${error.response.statusText}`);
|
|
90
|
+
} else {
|
|
91
|
+
throw new Error(`API error: ${error.message}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function groundedSearchFilter({
|
|
97
|
+
products,
|
|
98
|
+
groundingSummary,
|
|
99
|
+
apiKey,
|
|
100
|
+
}: {
|
|
101
|
+
products: any[];
|
|
102
|
+
groundingSummary: string;
|
|
103
|
+
apiKey: string;
|
|
104
|
+
}): Promise<any> {
|
|
105
|
+
const productList = products
|
|
106
|
+
.map((p, i) => buildProductEntry(p, i))
|
|
107
|
+
.join('\n');
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const response = await axios.post(
|
|
111
|
+
'https://api.nyris.io/api/ai-analysis/filtering-items',
|
|
112
|
+
{
|
|
113
|
+
itemsDataSummary: productList,
|
|
114
|
+
groundingSummary,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
headers: {
|
|
118
|
+
'x-api-key': apiKey || window.settings.aiApiKey || '',
|
|
119
|
+
'Content-Type': 'application/json',
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
return response.data;
|
|
124
|
+
} catch (error: any) {
|
|
125
|
+
if (error.response) {
|
|
126
|
+
throw new Error(`API error: ${error.response.statusText}`);
|
|
127
|
+
} else {
|
|
128
|
+
throw new Error(`API error: ${error.message}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function groundingSearchQuery({
|
|
134
|
+
groundingSummary,
|
|
135
|
+
apiKey,
|
|
136
|
+
}: {
|
|
137
|
+
groundingSummary: string;
|
|
138
|
+
apiKey: string;
|
|
139
|
+
}): Promise<any> {
|
|
140
|
+
try {
|
|
141
|
+
const response = await axios.post(
|
|
142
|
+
'https://api.nyris.io/api/ai-analysis/search-query',
|
|
143
|
+
{
|
|
144
|
+
groundingSummary,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
headers: {
|
|
148
|
+
'x-api-key': apiKey || window.settings.aiApiKey || '',
|
|
149
|
+
'Content-Type': 'application/json',
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
return response.data;
|
|
154
|
+
} catch (error: any) {
|
|
155
|
+
if (error.response) {
|
|
156
|
+
throw new Error(`API error: ${error.response.statusText}`);
|
|
157
|
+
} else {
|
|
158
|
+
throw new Error(`API error: ${error.message}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -5,6 +5,7 @@ import { initialState } from './misc.initialstate';
|
|
|
5
5
|
const miscSlice: StateCreator<MiscState & MiscAction> = set => ({
|
|
6
6
|
...initialState,
|
|
7
7
|
setMetaFilter: filter => set(state => ({ metaFilter: filter })),
|
|
8
|
+
setGroundingQuery: query => set(state => ({ groundingQuery: query })),
|
|
8
9
|
});
|
|
9
10
|
|
|
10
11
|
export default miscSlice;
|
|
@@ -10,6 +10,29 @@ const filterSlice: StateCreator<FilterState & FilterAction> = set => ({
|
|
|
10
10
|
set(state => ({ firstSearchPreFilter: filter })),
|
|
11
11
|
setSpecificationFilter: filter =>
|
|
12
12
|
set(state => ({ specificationFilter: filter })),
|
|
13
|
+
setPreFilterList: filter => set(state => ({ preFilterList: filter })),
|
|
14
|
+
setPreFilterLoading: isLoading =>
|
|
15
|
+
set(state => ({ preFilterLoading: isLoading })),
|
|
16
|
+
setPostFilterSelections: filters =>
|
|
17
|
+
set(state => ({ postFilterSelections: filters })),
|
|
18
|
+
togglePostFilterSelection: (attribute, value) =>
|
|
19
|
+
set(state => {
|
|
20
|
+
const current = state.postFilterSelections?.[attribute] || [];
|
|
21
|
+
const exists = current.includes(value);
|
|
22
|
+
const nextValues = exists
|
|
23
|
+
? current.filter(item => item !== value)
|
|
24
|
+
: [...current, value];
|
|
25
|
+
const nextSelections = { ...state.postFilterSelections };
|
|
26
|
+
|
|
27
|
+
if (nextValues.length > 0) {
|
|
28
|
+
nextSelections[attribute] = nextValues;
|
|
29
|
+
} else {
|
|
30
|
+
delete nextSelections[attribute];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { postFilterSelections: nextSelections };
|
|
34
|
+
}),
|
|
35
|
+
clearPostFilterSelections: () => set(state => ({ postFilterSelections: {} })),
|
|
13
36
|
});
|
|
14
37
|
|
|
15
38
|
export default filterSlice;
|
|
@@ -26,6 +26,21 @@ const productsSlice: StateCreator<ProductsState & ProductsAction> = set => ({
|
|
|
26
26
|
...analysis,
|
|
27
27
|
},
|
|
28
28
|
})),
|
|
29
|
+
setGoogleGroundingResponse: result =>
|
|
30
|
+
set(state => ({
|
|
31
|
+
googleGroundingResponse: result,
|
|
32
|
+
})),
|
|
33
|
+
|
|
34
|
+
setGroundingFilterResult: result =>
|
|
35
|
+
set(state => ({
|
|
36
|
+
groundingFilterResult: result,
|
|
37
|
+
})),
|
|
38
|
+
setShowingGroundingFilterResult: (val?: boolean) =>
|
|
39
|
+
set(state =>
|
|
40
|
+
val === undefined
|
|
41
|
+
? { showingGroundingFilterResult: !state.showingGroundingFilterResult }
|
|
42
|
+
: { showingGroundingFilterResult: val },
|
|
43
|
+
),
|
|
29
44
|
});
|
|
30
45
|
|
|
31
46
|
export default productsSlice;
|
package/src/stores/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RectCoords, Region } from '@nyris/nyris-api';
|
|
1
|
+
import { Filter, RectCoords, Region } from '@nyris/nyris-api';
|
|
2
2
|
|
|
3
3
|
export interface RequestImageState {
|
|
4
4
|
requestImages: HTMLCanvasElement[];
|
|
@@ -30,12 +30,14 @@ export interface LoadingState {
|
|
|
30
30
|
isFindApiLoading: boolean;
|
|
31
31
|
isAlgoliaLoading: boolean;
|
|
32
32
|
isCadenasLoaded: boolean;
|
|
33
|
+
isGoogleGroundingLoading: boolean;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
export interface LoadingAction {
|
|
36
37
|
setIsFindApiLoading: (isLoading: boolean) => void;
|
|
37
38
|
setIsAlgoliaLoading: (isLoading: boolean) => void;
|
|
38
39
|
setIsCadenasLoaded: (isLoading: boolean) => void;
|
|
40
|
+
setIsGoogleGroundingLoading: (isLoading: boolean) => void;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
export interface ProductsState {
|
|
@@ -54,6 +56,9 @@ export interface ProductsState {
|
|
|
54
56
|
};
|
|
55
57
|
|
|
56
58
|
specificationFilteredProducts: any[];
|
|
59
|
+
googleGroundingResponse: Record<string, any> | null;
|
|
60
|
+
groundingFilterResult: any[];
|
|
61
|
+
showingGroundingFilterResult: boolean;
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
export interface ProductsAction {
|
|
@@ -67,6 +72,9 @@ export interface ProductsAction {
|
|
|
67
72
|
specification: Record<string, string>;
|
|
68
73
|
}) => void;
|
|
69
74
|
setSpecificationFilteredProducts: (products: any[]) => void;
|
|
75
|
+
setGoogleGroundingResponse: (result: any | null) => void;
|
|
76
|
+
setGroundingFilterResult: (result: any[]) => void;
|
|
77
|
+
setShowingGroundingFilterResult: (val?: boolean) => void;
|
|
70
78
|
}
|
|
71
79
|
|
|
72
80
|
export interface DetectedRegionsState {
|
|
@@ -79,24 +87,34 @@ export interface DetectedRegionsAction {
|
|
|
79
87
|
|
|
80
88
|
export interface FilterState {
|
|
81
89
|
preFilter: Record<string, boolean>;
|
|
90
|
+
preFilterList: Filter[];
|
|
82
91
|
algoliaFilter: string;
|
|
83
92
|
firstSearchPreFilter: Record<string, boolean>;
|
|
84
93
|
specificationFilter: Record<string, string>;
|
|
94
|
+
postFilterSelections: Record<string, string[]>;
|
|
95
|
+
preFilterLoading: boolean;
|
|
85
96
|
}
|
|
86
97
|
|
|
87
98
|
export interface FilterAction {
|
|
88
99
|
setPreFilter: (query: Record<string, boolean>) => void;
|
|
100
|
+
setPreFilterList: (any: Filter[]) => void;
|
|
89
101
|
setFirstSearchPreFilter: (query: Record<string, boolean>) => void;
|
|
90
102
|
setAlgoliaFilter: (query: string) => void;
|
|
91
103
|
setSpecificationFilter: (query: Record<string, string>) => void;
|
|
104
|
+
setPostFilterSelections: (filters: Record<string, string[]>) => void;
|
|
105
|
+
togglePostFilterSelection: (attribute: string, value: string) => void;
|
|
106
|
+
clearPostFilterSelections: () => void;
|
|
107
|
+
setPreFilterLoading: (isLoading: boolean) => void;
|
|
92
108
|
}
|
|
93
109
|
|
|
94
110
|
export interface MiscState {
|
|
95
111
|
metaFilter: string;
|
|
112
|
+
groundingQuery: string;
|
|
96
113
|
}
|
|
97
114
|
|
|
98
115
|
export interface MiscAction {
|
|
99
116
|
setMetaFilter: (filter: string) => void;
|
|
117
|
+
setGroundingQuery: (query: string) => void;
|
|
100
118
|
}
|
|
101
119
|
|
|
102
120
|
export interface FeedbackState {
|
|
@@ -107,6 +125,14 @@ export interface FeedbackAction {
|
|
|
107
125
|
setShowFeedback: (show: boolean) => void;
|
|
108
126
|
}
|
|
109
127
|
|
|
128
|
+
export interface SidePanelState {
|
|
129
|
+
showSidePanel: boolean;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface SidePanelAction {
|
|
133
|
+
setShowSidePanel: (show: boolean) => void;
|
|
134
|
+
}
|
|
135
|
+
|
|
110
136
|
export interface SessionState {
|
|
111
137
|
requestId: string;
|
|
112
138
|
sessionId: string;
|
|
@@ -10,6 +10,10 @@ const loadingSlice: StateCreator<LoadingState & LoadingAction> = set => ({
|
|
|
10
10
|
set(state => ({ isFindApiLoading: isLoading })),
|
|
11
11
|
setIsCadenasLoaded: (isLoading: boolean) =>
|
|
12
12
|
set(state => ({ isCadenasLoaded: isLoading })),
|
|
13
|
+
setIsGoogleGroundingLoading: isLoading =>
|
|
14
|
+
set(state => ({
|
|
15
|
+
isGoogleGroundingLoading: isLoading,
|
|
16
|
+
})),
|
|
13
17
|
});
|
|
14
18
|
|
|
15
19
|
export default loadingSlice;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StateCreator } from 'zustand';
|
|
2
|
+
import { SidePanelAction, SidePanelState } from 'stores/types';
|
|
3
|
+
|
|
4
|
+
import { initialState } from './sidePanel.initialState';
|
|
5
|
+
|
|
6
|
+
const sidePanelSlice: StateCreator<SidePanelState & SidePanelAction> = set => ({
|
|
7
|
+
...initialState,
|
|
8
|
+
setShowSidePanel: (showSidePanel: boolean) => set(() => ({ showSidePanel })),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export default sidePanelSlice;
|
package/src/stores/ui/uiStore.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { create } from 'zustand';
|
|
2
2
|
import loadingSlice from './loading/loading.slice';
|
|
3
3
|
import feedbackSlice from './feedback/feedback.slice';
|
|
4
|
+
import sidePanelSlice from './sidePanel/sidePanel.slice';
|
|
4
5
|
|
|
5
6
|
type UiStore = ReturnType<typeof loadingSlice> &
|
|
6
|
-
ReturnType<typeof feedbackSlice
|
|
7
|
+
ReturnType<typeof feedbackSlice> &
|
|
8
|
+
ReturnType<typeof sidePanelSlice>;
|
|
7
9
|
|
|
8
10
|
const useUiStore = create<UiStore>()((...a) => ({
|
|
9
11
|
...loadingSlice(...a),
|
|
10
12
|
...feedbackSlice(...a),
|
|
13
|
+
...sidePanelSlice(...a),
|
|
11
14
|
}));
|
|
12
15
|
export default useUiStore;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
.cart-button {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
justify-content: center;
|
|
5
|
+
position: relative;
|
|
6
|
+
&.active {
|
|
7
|
+
background-color: #2b2c46;
|
|
8
|
+
}
|
|
9
|
+
&.disabled {
|
|
10
|
+
pointer-events: none;
|
|
11
|
+
cursor: default;
|
|
12
|
+
background-color: #801e1e;
|
|
13
|
+
}
|
|
14
|
+
.cart-amount {
|
|
15
|
+
position: absolute;
|
|
16
|
+
width: 16px;
|
|
17
|
+
height: 16px;
|
|
18
|
+
display: flex;
|
|
19
|
+
justify-content: center;
|
|
20
|
+
align-items: center;
|
|
21
|
+
border: 2px solid #fff;
|
|
22
|
+
background-color: #009ea6;
|
|
23
|
+
border-radius: 50%;
|
|
24
|
+
color: #fff;
|
|
25
|
+
font-size: 10px;
|
|
26
|
+
top: -6px;
|
|
27
|
+
right: -4px;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.cart {
|
|
32
|
+
position: absolute;
|
|
33
|
+
top: 0;
|
|
34
|
+
right: 0;
|
|
35
|
+
bottom: 0;
|
|
36
|
+
left: 0;
|
|
37
|
+
z-index: 51;
|
|
38
|
+
&-background {
|
|
39
|
+
top: 0;
|
|
40
|
+
right: 0;
|
|
41
|
+
bottom: 0;
|
|
42
|
+
left: 0;
|
|
43
|
+
position: absolute;
|
|
44
|
+
background-color: rgba(0, 0, 0, 0.2);
|
|
45
|
+
}
|
|
46
|
+
&-main {
|
|
47
|
+
height: 100vh;
|
|
48
|
+
width: 410px;
|
|
49
|
+
max-width: 100vw;
|
|
50
|
+
position: absolute;
|
|
51
|
+
right: 0;
|
|
52
|
+
background-color: #fff;
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-direction: column;
|
|
55
|
+
@media (max-width: 410px) {
|
|
56
|
+
width: 100vw;
|
|
57
|
+
min-width: 0;
|
|
58
|
+
}
|
|
59
|
+
&-header {
|
|
60
|
+
height: 52px;
|
|
61
|
+
display: flex;
|
|
62
|
+
flex-direction: row;
|
|
63
|
+
justify-content: space-between;
|
|
64
|
+
align-items: center;
|
|
65
|
+
padding: 8px 16px;
|
|
66
|
+
&-title {
|
|
67
|
+
font-weight: 600;
|
|
68
|
+
font-size: 24px;
|
|
69
|
+
}
|
|
70
|
+
&-buttons {
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-direction: row;
|
|
73
|
+
align-items: center;
|
|
74
|
+
.clear-all {
|
|
75
|
+
border: 1px solid #e31b5d;
|
|
76
|
+
padding: 4px 8px;
|
|
77
|
+
font-weight: 400;
|
|
78
|
+
font-size: 9px;
|
|
79
|
+
color: #e31b5d;
|
|
80
|
+
border-radius: 4px;
|
|
81
|
+
display: flex;
|
|
82
|
+
flex-direction: row;
|
|
83
|
+
align-items: center;
|
|
84
|
+
margin-right: 16px;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
&-body {
|
|
89
|
+
flex: 1;
|
|
90
|
+
background-color: #f3f3f5;
|
|
91
|
+
padding: 16px;
|
|
92
|
+
overflow-x: auto;
|
|
93
|
+
&-category {
|
|
94
|
+
font-family: 'Fira Sans', sans-serif;
|
|
95
|
+
font-weight: 600;
|
|
96
|
+
font-size: 16px;
|
|
97
|
+
margin-bottom: 16px;
|
|
98
|
+
}
|
|
99
|
+
&-parts {
|
|
100
|
+
display: flex;
|
|
101
|
+
flex-direction: column;
|
|
102
|
+
gap: 8px;
|
|
103
|
+
margin-bottom: 16px;
|
|
104
|
+
&-item {
|
|
105
|
+
width: 100%;
|
|
106
|
+
height: 66px;
|
|
107
|
+
padding: 8px;
|
|
108
|
+
border: 1px #e0e0e0 solid;
|
|
109
|
+
border-radius: 8px;
|
|
110
|
+
background-color: #fafafa;
|
|
111
|
+
display: flex;
|
|
112
|
+
flex-direction: row;
|
|
113
|
+
align-items: center;
|
|
114
|
+
justify-content: space-between;
|
|
115
|
+
&-column {
|
|
116
|
+
flex: 1;
|
|
117
|
+
display: flex;
|
|
118
|
+
flex-direction: row;
|
|
119
|
+
align-items: center;
|
|
120
|
+
}
|
|
121
|
+
img {
|
|
122
|
+
width: 50px;
|
|
123
|
+
height: 50px;
|
|
124
|
+
object-fit: cover;
|
|
125
|
+
background-color: #fff;
|
|
126
|
+
margin-right: 8px;
|
|
127
|
+
}
|
|
128
|
+
&-sku {
|
|
129
|
+
font-weight: 400;
|
|
130
|
+
font-size: 9px;
|
|
131
|
+
max-width: 150px;
|
|
132
|
+
word-break: break-all;
|
|
133
|
+
}
|
|
134
|
+
&-title {
|
|
135
|
+
font-weight: 600;
|
|
136
|
+
font-size: 12px;
|
|
137
|
+
max-width: 150px;
|
|
138
|
+
word-break: break-all;
|
|
139
|
+
}
|
|
140
|
+
&-quantity {
|
|
141
|
+
display: flex;
|
|
142
|
+
flex-direction: row;
|
|
143
|
+
align-items: center;
|
|
144
|
+
gap: 16px;
|
|
145
|
+
padding-right: 16px;
|
|
146
|
+
margin-left: auto;
|
|
147
|
+
button {
|
|
148
|
+
width: 16px;
|
|
149
|
+
height: 16px;
|
|
150
|
+
border-radius: 50%;
|
|
151
|
+
background-color: #f3f3f5;
|
|
152
|
+
}
|
|
153
|
+
&-number {
|
|
154
|
+
min-width: 22px;
|
|
155
|
+
height: 22px;
|
|
156
|
+
background-color: #fff;
|
|
157
|
+
border-radius: 4px;
|
|
158
|
+
border: 1px solid #f3f3f5;
|
|
159
|
+
display: flex;
|
|
160
|
+
align-items: center;
|
|
161
|
+
justify-content: center;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
&-remove {
|
|
165
|
+
width: 28px;
|
|
166
|
+
height: 28px;
|
|
167
|
+
background-color: #ffe5ef4d;
|
|
168
|
+
padding: 6px;
|
|
169
|
+
border-radius: 4px;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
&-empty {
|
|
174
|
+
width: 100%;
|
|
175
|
+
height: 100%;
|
|
176
|
+
display: flex;
|
|
177
|
+
flex-direction: column;
|
|
178
|
+
align-items: center;
|
|
179
|
+
justify-content: center;
|
|
180
|
+
&-title {
|
|
181
|
+
font-weight: 600;
|
|
182
|
+
font-size: 16px;
|
|
183
|
+
line-height: 16px;
|
|
184
|
+
letter-spacing: 0.16px;
|
|
185
|
+
}
|
|
186
|
+
&-text {
|
|
187
|
+
font-weight: 400;
|
|
188
|
+
line-height: 16px;
|
|
189
|
+
letter-spacing: 0.16px;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
.cart-download-button {
|
|
193
|
+
background-color: #3e36dc;
|
|
194
|
+
color: #fff;
|
|
195
|
+
width: 100%;
|
|
196
|
+
height: 48px;
|
|
197
|
+
display: flex;
|
|
198
|
+
flex-direction: row;
|
|
199
|
+
align-items: center;
|
|
200
|
+
justify-content: center;
|
|
201
|
+
font-weight: 600;
|
|
202
|
+
font-size: 16px;
|
|
203
|
+
border-radius: 8px;
|
|
204
|
+
svg {
|
|
205
|
+
margin-right: 8px;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
package/src/styles/common.scss
CHANGED
|
@@ -12,6 +12,16 @@
|
|
|
12
12
|
-webkit-box-orient: vertical;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
.max-line-2 {
|
|
16
|
+
word-break: break-word;
|
|
17
|
+
overflow: hidden;
|
|
18
|
+
text-overflow: ellipsis;
|
|
19
|
+
display: -webkit-box;
|
|
20
|
+
max-height: 66px; /* fallback */
|
|
21
|
+
-webkit-line-clamp: 2; /* number of lines to show */
|
|
22
|
+
-webkit-box-orient: vertical;
|
|
23
|
+
}
|
|
24
|
+
|
|
15
25
|
.box-wrap-loading {
|
|
16
26
|
position: fixed;
|
|
17
27
|
background: #00000075;
|
package/src/translations.ts
CHANGED
|
@@ -71,7 +71,7 @@ export const translations = {
|
|
|
71
71
|
'Labels': 'Labels',
|
|
72
72
|
'Barcodes': 'Barcodes',
|
|
73
73
|
'Nameplate not matching': 'Nameplate detected, but {{prefilter_value}} was not found in the list. You can select the correct {{preFilterTitle}} to continue.',
|
|
74
|
-
'Extracted details from the nameplate could not be matched': '
|
|
74
|
+
'Extracted details from the nameplate could not be matched': 'The extracted details could not be matched to a known {{preFilterTitle}}. Please select a different {{preFilterTitle}} and continue searching.',
|
|
75
75
|
'Showing results for machine': `Showing results for {{prefilter_title}} '{{prefilter_value}}'`,
|
|
76
76
|
'No matches found for': `No matches found for {{prefilter_title}} '{{prefilter_value}}'. Showing all items instead.`,
|
|
77
77
|
},
|
|
@@ -150,7 +150,7 @@ export const translations = {
|
|
|
150
150
|
'Labels': 'Etiketten',
|
|
151
151
|
'Barcodes': 'Barcodes',
|
|
152
152
|
'Nameplate not matching': 'Typenschild erkannt, aber {{prefilter_value}} wurde nicht in der Liste gefunden. Sie können die richtigen {{preFilterTitle}}, um fortzufahren.',
|
|
153
|
-
'Extracted details from the nameplate could not be matched': 'Die
|
|
153
|
+
'Extracted details from the nameplate could not be matched': 'Die extrahierten Details konnten keinem bekannten {{preFilterTitle}} zugeordnet werden. Bitte wählen Sie einen anderen {{preFilterTitle}} aus und setzen Sie die Suche fort.',
|
|
154
154
|
'Showing results for machine': `Ergebnisse für {{prefilter_title}} '{{prefilter_value}}' anzeigen`,
|
|
155
155
|
'No matches found for': `Keine Übereinstimmungen gefunden für {{prefilter_title}} '{{prefilter_value}}'. Alle Artikel anzeigen stattdessen.`,
|
|
156
156
|
},
|
|
@@ -229,8 +229,8 @@ export const translations = {
|
|
|
229
229
|
'Parts': 'Peças',
|
|
230
230
|
'Labels': 'Etiquetas',
|
|
231
231
|
'Barcodes': 'Códigos de barras',
|
|
232
|
-
'Nameplate not matching': 'Placa de identificação detetada, mas {{prefilter_value}} não foi encontrado na lista. Pode selecionar os {{preFilterTitle}} corretos para continuar.',
|
|
233
|
-
|
|
232
|
+
'Nameplate not matching': 'Placa de identificação detetada, mas {{prefilter_value}} não foi encontrado na lista. Pode selecionar os {{preFilterTitle}} corretos para continuar.',
|
|
233
|
+
'Extracted details from the nameplate could not be matched': 'Os detalhes extraídos não puderam ser correspondidos a um {{preFilterTitle}} conhecido. Selecione um {{preFilterTitle}} diferente e continue a pesquisa.',
|
|
234
234
|
'Showing results for machine': `Mostrando resultados para {{prefilter_title}} '{{prefilter_value}}'`,
|
|
235
235
|
'No matches found for': `Não foram encontrados resultados para {{prefilter_title}} '{{prefilter_value}}'. Mostrando todos os itens em vez disso.`,
|
|
236
236
|
},
|