@propeller-commerce/propeller-v2-react-ui 0.6.1 → 0.7.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.
- package/CHANGELOG.md +23 -0
- package/dist/index.cjs +497 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +202 -1
- package/dist/index.d.ts +202 -1
- package/dist/index.js +496 -21
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2498,6 +2498,140 @@ function useProductSearch(options) {
|
|
|
2498
2498
|
goToPage: pagination.goToPage
|
|
2499
2499
|
};
|
|
2500
2500
|
}
|
|
2501
|
+
function toMatch(item, language) {
|
|
2502
|
+
const isCluster = "clusterId" in item;
|
|
2503
|
+
const displayItem = isCluster ? item.defaultProduct : item;
|
|
2504
|
+
const productId = displayItem?.productId ?? item.productId;
|
|
2505
|
+
const clusterId = isCluster ? item.clusterId : void 0;
|
|
2506
|
+
const name = language && item.names?.find((n) => n.language === language)?.value || item.names?.[0]?.value || "Product";
|
|
2507
|
+
const netPrice = displayItem?.price?.net ?? 0;
|
|
2508
|
+
const grossPrice = displayItem?.price?.gross ?? 0;
|
|
2509
|
+
const minQuantity = Math.max(1, displayItem?.minimumQuantity ?? 1);
|
|
2510
|
+
const imageUrl = isCluster ? propellerV2CoreUi.getClusterImageUrl(item) : propellerV2CoreUi.getProductImageUrl(item);
|
|
2511
|
+
return {
|
|
2512
|
+
productId,
|
|
2513
|
+
clusterId,
|
|
2514
|
+
name,
|
|
2515
|
+
sku: item.sku || displayItem?.sku || "",
|
|
2516
|
+
netPrice,
|
|
2517
|
+
grossPrice,
|
|
2518
|
+
minQuantity,
|
|
2519
|
+
imageUrl
|
|
2520
|
+
};
|
|
2521
|
+
}
|
|
2522
|
+
function useQuickOrder(options) {
|
|
2523
|
+
const {
|
|
2524
|
+
graphqlClient,
|
|
2525
|
+
user,
|
|
2526
|
+
companyId,
|
|
2527
|
+
configuration = {},
|
|
2528
|
+
onCartCreated,
|
|
2529
|
+
afterAddToCart
|
|
2530
|
+
} = options;
|
|
2531
|
+
const language = options.language || "NL";
|
|
2532
|
+
const searchLimit = options.searchLimit ?? 8;
|
|
2533
|
+
const [submitting, setSubmitting] = React6.useState(false);
|
|
2534
|
+
const [error, setError] = React6.useState(null);
|
|
2535
|
+
const searchProducts = React6.useCallback(
|
|
2536
|
+
async (term) => {
|
|
2537
|
+
const trimmed = term.trim();
|
|
2538
|
+
if (!trimmed || !graphqlClient) return [];
|
|
2539
|
+
try {
|
|
2540
|
+
const service = propellerV2CoreUi.createServices(graphqlClient).product;
|
|
2541
|
+
const input = {
|
|
2542
|
+
term: trimmed,
|
|
2543
|
+
language,
|
|
2544
|
+
page: 1,
|
|
2545
|
+
offset: searchLimit,
|
|
2546
|
+
statuses: [propellerSdkV2.ProductStatus.A, propellerSdkV2.ProductStatus.P, propellerSdkV2.ProductStatus.T, propellerSdkV2.ProductStatus.S],
|
|
2547
|
+
sortInputs: [{ field: propellerSdkV2.ProductSortField.RELEVANCE, order: propellerSdkV2.SortOrder.DESC }],
|
|
2548
|
+
searchFields: [
|
|
2549
|
+
{
|
|
2550
|
+
fieldNames: [
|
|
2551
|
+
propellerSdkV2.ProductSearchableField.SKU,
|
|
2552
|
+
propellerSdkV2.ProductSearchableField.NAME,
|
|
2553
|
+
propellerSdkV2.ProductSearchableField.KEYWORDS,
|
|
2554
|
+
propellerSdkV2.ProductSearchableField.CUSTOM_KEYWORDS
|
|
2555
|
+
],
|
|
2556
|
+
boost: 5
|
|
2557
|
+
},
|
|
2558
|
+
{
|
|
2559
|
+
fieldNames: [
|
|
2560
|
+
propellerSdkV2.ProductSearchableField.MANUFACTURER_CODE,
|
|
2561
|
+
propellerSdkV2.ProductSearchableField.EAN_CODE,
|
|
2562
|
+
propellerSdkV2.ProductSearchableField.BAR_CODE,
|
|
2563
|
+
propellerSdkV2.ProductSearchableField.SUPPLIER_CODE,
|
|
2564
|
+
propellerSdkV2.ProductSearchableField.PRODUCT_ID
|
|
2565
|
+
],
|
|
2566
|
+
boost: 1
|
|
2567
|
+
}
|
|
2568
|
+
]
|
|
2569
|
+
};
|
|
2570
|
+
const variables = {
|
|
2571
|
+
input,
|
|
2572
|
+
language,
|
|
2573
|
+
imageVariantFilters: configuration.imageVariantFiltersSmall
|
|
2574
|
+
};
|
|
2575
|
+
const result = await service.getProducts(variables);
|
|
2576
|
+
const items = result?.items ?? [];
|
|
2577
|
+
return items.map((it) => toMatch(it, language));
|
|
2578
|
+
} catch {
|
|
2579
|
+
return [];
|
|
2580
|
+
}
|
|
2581
|
+
},
|
|
2582
|
+
[graphqlClient, language, searchLimit, configuration]
|
|
2583
|
+
);
|
|
2584
|
+
const submit = React6.useCallback(
|
|
2585
|
+
async (lines) => {
|
|
2586
|
+
const valid = lines.filter((l) => l.productId && (l.quantity ?? 0) > 0);
|
|
2587
|
+
if (!valid.length) return { success: false, error: "No items to add" };
|
|
2588
|
+
if (!graphqlClient) return { success: false, error: "No GraphQL client" };
|
|
2589
|
+
setSubmitting(true);
|
|
2590
|
+
setError(null);
|
|
2591
|
+
try {
|
|
2592
|
+
const services = propellerV2CoreUi.createServices(graphqlClient);
|
|
2593
|
+
const cart = await initCart({
|
|
2594
|
+
services,
|
|
2595
|
+
user: user ?? null,
|
|
2596
|
+
companyId,
|
|
2597
|
+
language,
|
|
2598
|
+
imageSearchFilters: configuration.imageSearchFiltersGrid,
|
|
2599
|
+
imageVariantFilters: configuration.imageVariantFiltersSmall,
|
|
2600
|
+
onCartCreated
|
|
2601
|
+
});
|
|
2602
|
+
const items = valid.map((l) => ({
|
|
2603
|
+
productId: l.productId,
|
|
2604
|
+
quantity: l.quantity,
|
|
2605
|
+
...l.clusterId ? { clusterId: l.clusterId } : {}
|
|
2606
|
+
}));
|
|
2607
|
+
const bulk = await services.cart.bulkUpdateCartItems({
|
|
2608
|
+
input: { cartId: cart.cartId, items }
|
|
2609
|
+
});
|
|
2610
|
+
const updated = await services.cart.getCart({
|
|
2611
|
+
cartId: cart.cartId,
|
|
2612
|
+
language,
|
|
2613
|
+
imageSearchFilters: configuration.imageSearchFiltersGrid,
|
|
2614
|
+
imageVariantFilters: configuration.imageVariantFiltersSmall
|
|
2615
|
+
});
|
|
2616
|
+
const finalCart = updated ?? cart;
|
|
2617
|
+
afterAddToCart?.(finalCart);
|
|
2618
|
+
return {
|
|
2619
|
+
success: true,
|
|
2620
|
+
cart: finalCart,
|
|
2621
|
+
added: (bulk?.created ?? 0) + (bulk?.updated ?? 0)
|
|
2622
|
+
};
|
|
2623
|
+
} catch (e) {
|
|
2624
|
+
const msg = e instanceof Error ? e.message : "Failed to add items to cart";
|
|
2625
|
+
setError(msg);
|
|
2626
|
+
return { success: false, error: msg };
|
|
2627
|
+
} finally {
|
|
2628
|
+
setSubmitting(false);
|
|
2629
|
+
}
|
|
2630
|
+
},
|
|
2631
|
+
[graphqlClient, user, companyId, language, configuration, onCartCreated, afterAddToCart]
|
|
2632
|
+
);
|
|
2633
|
+
return { submitting, error, searchProducts, submit };
|
|
2634
|
+
}
|
|
2501
2635
|
var inflightFetches3 = /* @__PURE__ */ new Set();
|
|
2502
2636
|
var STOREFRONT_STATUSES = [
|
|
2503
2637
|
propellerSdkV2.ProductStatus.A,
|
|
@@ -6385,7 +6519,7 @@ function CartItem(rawProps) {
|
|
|
6385
6519
|
}
|
|
6386
6520
|
return "#";
|
|
6387
6521
|
}
|
|
6388
|
-
function
|
|
6522
|
+
function getProductImageUrl6() {
|
|
6389
6523
|
return props.cartItem.product?.media?.images?.items?.[0]?.imageVariants?.[0]?.url || "";
|
|
6390
6524
|
}
|
|
6391
6525
|
function getProductSku3() {
|
|
@@ -6569,7 +6703,7 @@ function CartItem(rawProps) {
|
|
|
6569
6703
|
derived: {
|
|
6570
6704
|
name: getProductName5(),
|
|
6571
6705
|
sku: getProductSku3(),
|
|
6572
|
-
imageUrl:
|
|
6706
|
+
imageUrl: getProductImageUrl6(),
|
|
6573
6707
|
productUrl: getProductUrl2(),
|
|
6574
6708
|
isBundle: isBundleItem(),
|
|
6575
6709
|
bundleName: getBundleName(),
|
|
@@ -9543,7 +9677,7 @@ function GridToolbar(rawProps) {
|
|
|
9543
9677
|
const [currentSortOrder, setCurrentSortOrder] = React6.useState(propellerSdkV2.SortOrder.DESC);
|
|
9544
9678
|
const [currentOffset, setCurrentOffset] = React6.useState(12);
|
|
9545
9679
|
const [currentViewMode, setCurrentViewMode] = React6.useState("grid");
|
|
9546
|
-
function
|
|
9680
|
+
function getLabel55(key) {
|
|
9547
9681
|
const labels = props.labels || {};
|
|
9548
9682
|
return labels[key] !== void 0 ? labels[key] : DEFAULT_LABELS[key] || key;
|
|
9549
9683
|
}
|
|
@@ -9622,7 +9756,7 @@ function GridToolbar(rawProps) {
|
|
|
9622
9756
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-grid-toolbar__count text-sm text-muted-foreground font-medium", children: props.itemsFound > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
9623
9757
|
props.itemsFound,
|
|
9624
9758
|
"\xA0",
|
|
9625
|
-
props.itemsFound === 1 ?
|
|
9759
|
+
props.itemsFound === 1 ? getLabel55("productSingular") : getLabel55("productPlural")
|
|
9626
9760
|
] }) : null }),
|
|
9627
9761
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "propeller-grid-toolbar__controls flex flex-wrap items-center gap-3", children: [
|
|
9628
9762
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -9633,7 +9767,7 @@ function GridToolbar(rawProps) {
|
|
|
9633
9767
|
onChange: (e) => handleOffsetChange(parseInt(e.target.value)),
|
|
9634
9768
|
children: getOffsetOptions()?.map((n) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: n, children: [
|
|
9635
9769
|
n,
|
|
9636
|
-
|
|
9770
|
+
getLabel55("perPage")
|
|
9637
9771
|
] }, n))
|
|
9638
9772
|
}
|
|
9639
9773
|
),
|
|
@@ -9649,7 +9783,7 @@ function GridToolbar(rawProps) {
|
|
|
9649
9783
|
{
|
|
9650
9784
|
value: field,
|
|
9651
9785
|
disabled: field === "PRICE" && isPriceSortDisabled(),
|
|
9652
|
-
children:
|
|
9786
|
+
children: getLabel55(field)
|
|
9653
9787
|
},
|
|
9654
9788
|
field
|
|
9655
9789
|
))
|
|
@@ -9662,8 +9796,8 @@ function GridToolbar(rawProps) {
|
|
|
9662
9796
|
value: currentSortOrder,
|
|
9663
9797
|
onChange: (e) => handleSortOrderChange(e.target.value),
|
|
9664
9798
|
children: [
|
|
9665
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: propellerSdkV2.SortOrder.ASC, children:
|
|
9666
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: propellerSdkV2.SortOrder.DESC, children:
|
|
9799
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: propellerSdkV2.SortOrder.ASC, children: getLabel55("ASC") }),
|
|
9800
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: propellerSdkV2.SortOrder.DESC, children: getLabel55("DESC") })
|
|
9667
9801
|
]
|
|
9668
9802
|
}
|
|
9669
9803
|
),
|
|
@@ -9673,7 +9807,7 @@ function GridToolbar(rawProps) {
|
|
|
9673
9807
|
type: "button",
|
|
9674
9808
|
className: "propeller-grid-toolbar__view-toggle h-9 w-9 flex items-center justify-center rounded-control border border-input bg-transparent hover:bg-accent hover:text-accent-foreground transition-colors",
|
|
9675
9809
|
onClick: (event) => handleViewChange(),
|
|
9676
|
-
title: currentViewMode === "grid" ?
|
|
9810
|
+
title: currentViewMode === "grid" ? getLabel55("switchToList") : getLabel55("switchToGrid"),
|
|
9677
9811
|
children: [
|
|
9678
9812
|
currentViewMode === "grid" ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9679
9813
|
"svg",
|
|
@@ -9731,7 +9865,7 @@ function GridToolbar(rawProps) {
|
|
|
9731
9865
|
onClick: (event) => {
|
|
9732
9866
|
if (props.onClearFilters) props.onClearFilters();
|
|
9733
9867
|
},
|
|
9734
|
-
children:
|
|
9868
|
+
children: getLabel55("clearAll")
|
|
9735
9869
|
}
|
|
9736
9870
|
),
|
|
9737
9871
|
props.priceFilterMin !== void 0 || props.priceFilterMax !== void 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -9742,7 +9876,7 @@ function GridToolbar(rawProps) {
|
|
|
9742
9876
|
if (props.onPriceFilterRemove) props.onPriceFilterRemove();
|
|
9743
9877
|
},
|
|
9744
9878
|
children: [
|
|
9745
|
-
|
|
9879
|
+
getLabel55("price"),
|
|
9746
9880
|
": \u20AC",
|
|
9747
9881
|
props.priceFilterMin ?? 0,
|
|
9748
9882
|
" \u2013 \u20AC",
|
|
@@ -9779,7 +9913,7 @@ var DEFAULT_LABELS2 = {
|
|
|
9779
9913
|
of: "of"
|
|
9780
9914
|
};
|
|
9781
9915
|
function GridPagination(props) {
|
|
9782
|
-
function
|
|
9916
|
+
function getLabel55(key) {
|
|
9783
9917
|
const labels = props.labels || {};
|
|
9784
9918
|
return labels[key] !== void 0 ? labels[key] : DEFAULT_LABELS2[key] || key;
|
|
9785
9919
|
}
|
|
@@ -9852,15 +9986,15 @@ function GridPagination(props) {
|
|
|
9852
9986
|
className: "propeller-grid-pagination__btn propeller-grid-pagination__btn--prev inline-flex items-center rounded-control border border-input bg-card px-4 py-2 text-sm font-medium text-foreground shadow-sm hover:bg-surface-hover disabled:opacity-40 disabled:cursor-not-allowed",
|
|
9853
9987
|
disabled: currentPage === 1,
|
|
9854
9988
|
onClick: () => handlePageChange(currentPage - 1),
|
|
9855
|
-
children:
|
|
9989
|
+
children: getLabel55("previous")
|
|
9856
9990
|
}
|
|
9857
9991
|
),
|
|
9858
9992
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "propeller-grid-pagination__info px-2 text-sm font-medium text-muted-foreground", children: [
|
|
9859
|
-
|
|
9993
|
+
getLabel55("page"),
|
|
9860
9994
|
"\xA0",
|
|
9861
9995
|
currentPage,
|
|
9862
9996
|
"\xA0",
|
|
9863
|
-
|
|
9997
|
+
getLabel55("of"),
|
|
9864
9998
|
"\xA0",
|
|
9865
9999
|
totalPages
|
|
9866
10000
|
] }),
|
|
@@ -9871,7 +10005,7 @@ function GridPagination(props) {
|
|
|
9871
10005
|
className: "propeller-grid-pagination__btn propeller-grid-pagination__btn--next inline-flex items-center rounded-control border border-input bg-card px-4 py-2 text-sm font-medium text-foreground shadow-sm hover:bg-surface-hover disabled:opacity-40 disabled:cursor-not-allowed",
|
|
9872
10006
|
disabled: currentPage === totalPages,
|
|
9873
10007
|
onClick: () => handlePageChange(currentPage + 1),
|
|
9874
|
-
children:
|
|
10008
|
+
children: getLabel55("next")
|
|
9875
10009
|
}
|
|
9876
10010
|
)
|
|
9877
10011
|
] }) : null,
|
|
@@ -9883,7 +10017,7 @@ function GridPagination(props) {
|
|
|
9883
10017
|
className: "propeller-grid-pagination__btn propeller-grid-pagination__btn--prev inline-flex items-center rounded-control border border-input bg-card px-3 py-2 text-sm font-medium text-foreground shadow-sm hover:bg-surface-hover disabled:opacity-40 disabled:cursor-not-allowed",
|
|
9884
10018
|
disabled: currentPage === 1,
|
|
9885
10019
|
onClick: () => handlePageChange(currentPage - 1),
|
|
9886
|
-
children:
|
|
10020
|
+
children: getLabel55("previous")
|
|
9887
10021
|
}
|
|
9888
10022
|
),
|
|
9889
10023
|
getFullPages().map((item, idx) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -9910,7 +10044,7 @@ function GridPagination(props) {
|
|
|
9910
10044
|
className: "propeller-grid-pagination__btn propeller-grid-pagination__btn--next inline-flex items-center rounded-control border border-input bg-card px-3 py-2 text-sm font-medium text-foreground shadow-sm hover:bg-surface-hover disabled:opacity-40 disabled:cursor-not-allowed",
|
|
9911
10045
|
disabled: currentPage === totalPages,
|
|
9912
10046
|
onClick: () => handlePageChange(currentPage + 1),
|
|
9913
|
-
children:
|
|
10047
|
+
children: getLabel55("next")
|
|
9914
10048
|
}
|
|
9915
10049
|
)
|
|
9916
10050
|
] }) : null
|
|
@@ -10389,7 +10523,7 @@ function formatPrice9(price, currency) {
|
|
|
10389
10523
|
function getProductName2(product) {
|
|
10390
10524
|
return product.names?.[0]?.value || `Product ${product.productId}`;
|
|
10391
10525
|
}
|
|
10392
|
-
function
|
|
10526
|
+
function getProductImageUrl4(product) {
|
|
10393
10527
|
const media = product.media;
|
|
10394
10528
|
if (media?.images?.items && Array.isArray(media.images.items) && media.images.items.length > 0) {
|
|
10395
10529
|
const firstImage = media.images.items[0];
|
|
@@ -10418,7 +10552,7 @@ function ClusterOptions(props) {
|
|
|
10418
10552
|
(p) => p.productId.toString() === selectedProductId
|
|
10419
10553
|
);
|
|
10420
10554
|
if (selectedProduct) {
|
|
10421
|
-
previewImageUrl =
|
|
10555
|
+
previewImageUrl = getProductImageUrl4(selectedProduct);
|
|
10422
10556
|
previewName = getProductName2(selectedProduct);
|
|
10423
10557
|
previewPrice = formatPrice9(selectedProduct.price?.gross || 0, props.currency ?? "\u20AC");
|
|
10424
10558
|
}
|
|
@@ -13795,6 +13929,347 @@ function OrderList(rawProps) {
|
|
|
13795
13929
|
] });
|
|
13796
13930
|
}
|
|
13797
13931
|
var OrderList_default = OrderList;
|
|
13932
|
+
var ROW_SEQ = 0;
|
|
13933
|
+
function blankRow() {
|
|
13934
|
+
ROW_SEQ += 1;
|
|
13935
|
+
return {
|
|
13936
|
+
key: `qo-${ROW_SEQ}`,
|
|
13937
|
+
code: "",
|
|
13938
|
+
productId: null,
|
|
13939
|
+
name: "",
|
|
13940
|
+
netPrice: 0,
|
|
13941
|
+
quantity: 1,
|
|
13942
|
+
minQuantity: 1,
|
|
13943
|
+
matches: [],
|
|
13944
|
+
searching: false,
|
|
13945
|
+
searched: false
|
|
13946
|
+
};
|
|
13947
|
+
}
|
|
13948
|
+
function QuickOrderInner(props) {
|
|
13949
|
+
const {
|
|
13950
|
+
companyId,
|
|
13951
|
+
language = "NL",
|
|
13952
|
+
currency = "\u20AC",
|
|
13953
|
+
configuration,
|
|
13954
|
+
initialRows = 5,
|
|
13955
|
+
searchThreshold = 3,
|
|
13956
|
+
debounceMs = 300,
|
|
13957
|
+
parseSpreadsheet,
|
|
13958
|
+
templateUrl,
|
|
13959
|
+
maxUploadBytes = 2 * 1024 * 1024,
|
|
13960
|
+
maxUploadRows = 500,
|
|
13961
|
+
onCartCreated,
|
|
13962
|
+
afterAddToCart,
|
|
13963
|
+
onMissingCodes,
|
|
13964
|
+
className,
|
|
13965
|
+
labels
|
|
13966
|
+
} = props;
|
|
13967
|
+
const user = props.user ?? null;
|
|
13968
|
+
const graphqlClient = props.graphqlClient;
|
|
13969
|
+
const displayPrice = (n) => props.formatPrice ? props.formatPrice(n) : propellerV2CoreUi.formatPrice(n, { symbol: currency });
|
|
13970
|
+
const L = (key, fallback) => propellerV2CoreUi.getLabel(labels, key, fallback);
|
|
13971
|
+
const { submitting, searchProducts, submit } = useQuickOrder({
|
|
13972
|
+
graphqlClient,
|
|
13973
|
+
user,
|
|
13974
|
+
companyId,
|
|
13975
|
+
language,
|
|
13976
|
+
configuration,
|
|
13977
|
+
onCartCreated,
|
|
13978
|
+
afterAddToCart
|
|
13979
|
+
});
|
|
13980
|
+
const [rows, setRows] = React6.useState(
|
|
13981
|
+
() => Array.from({ length: Math.max(1, initialRows) }, blankRow)
|
|
13982
|
+
);
|
|
13983
|
+
const [missing, setMissing] = React6.useState([]);
|
|
13984
|
+
const [uploadError, setUploadError] = React6.useState(null);
|
|
13985
|
+
const [uploading, setUploading] = React6.useState(false);
|
|
13986
|
+
const [notice, setNotice] = React6.useState(null);
|
|
13987
|
+
const fileInputRef = React6.useRef(null);
|
|
13988
|
+
const searchTimers = React6.useRef({});
|
|
13989
|
+
const patchRow = React6.useCallback((key, patch) => {
|
|
13990
|
+
setRows((prev) => prev.map((r) => r.key === key ? { ...r, ...patch } : r));
|
|
13991
|
+
}, []);
|
|
13992
|
+
const onCodeInput = React6.useCallback(
|
|
13993
|
+
(key, value) => {
|
|
13994
|
+
patchRow(key, { code: value, productId: null, name: "", netPrice: 0, searched: false });
|
|
13995
|
+
setNotice(null);
|
|
13996
|
+
const timers = searchTimers.current;
|
|
13997
|
+
if (timers[key]) clearTimeout(timers[key]);
|
|
13998
|
+
if (value.trim().length < searchThreshold) {
|
|
13999
|
+
patchRow(key, { matches: [], searching: false, searched: false });
|
|
14000
|
+
return;
|
|
14001
|
+
}
|
|
14002
|
+
patchRow(key, { searching: true });
|
|
14003
|
+
timers[key] = setTimeout(async () => {
|
|
14004
|
+
const results = await searchProducts(value);
|
|
14005
|
+
patchRow(key, { matches: results, searching: false, searched: true });
|
|
14006
|
+
}, debounceMs);
|
|
14007
|
+
},
|
|
14008
|
+
[patchRow, searchProducts, searchThreshold, debounceMs]
|
|
14009
|
+
);
|
|
14010
|
+
const selectMatch = React6.useCallback(
|
|
14011
|
+
(key, match) => {
|
|
14012
|
+
const dup = rows.some((r) => r.key !== key && r.productId && r.code === match.sku);
|
|
14013
|
+
if (dup) {
|
|
14014
|
+
setNotice(L("alreadyInList", "Product is already in the list"));
|
|
14015
|
+
patchRow(key, { code: "", matches: [], productId: null, name: "", netPrice: 0, searched: false });
|
|
14016
|
+
return;
|
|
14017
|
+
}
|
|
14018
|
+
patchRow(key, {
|
|
14019
|
+
code: match.sku,
|
|
14020
|
+
productId: match.productId,
|
|
14021
|
+
clusterId: match.clusterId,
|
|
14022
|
+
name: match.name,
|
|
14023
|
+
netPrice: match.netPrice,
|
|
14024
|
+
quantity: match.minQuantity,
|
|
14025
|
+
minQuantity: match.minQuantity,
|
|
14026
|
+
matches: [],
|
|
14027
|
+
searching: false,
|
|
14028
|
+
searched: false
|
|
14029
|
+
});
|
|
14030
|
+
},
|
|
14031
|
+
[rows, patchRow, L]
|
|
14032
|
+
);
|
|
14033
|
+
const setQuantity = React6.useCallback(
|
|
14034
|
+
(key, raw) => {
|
|
14035
|
+
const n = parseInt(raw, 10);
|
|
14036
|
+
setRows(
|
|
14037
|
+
(prev) => prev.map(
|
|
14038
|
+
(r) => r.key === key ? { ...r, quantity: Number.isFinite(n) && n > 0 ? Math.max(r.minQuantity, n) : r.minQuantity } : r
|
|
14039
|
+
)
|
|
14040
|
+
);
|
|
14041
|
+
},
|
|
14042
|
+
[]
|
|
14043
|
+
);
|
|
14044
|
+
const addRow = React6.useCallback(() => setRows((prev) => [...prev, blankRow()]), []);
|
|
14045
|
+
const removeRow = React6.useCallback(
|
|
14046
|
+
(key) => setRows((prev) => prev.length > 1 ? prev.filter((r) => r.key !== key) : prev),
|
|
14047
|
+
[]
|
|
14048
|
+
);
|
|
14049
|
+
const resolvedCount = React6.useMemo(() => rows.filter((r) => r.productId).length, [rows]);
|
|
14050
|
+
const onFileChosen = React6.useCallback(
|
|
14051
|
+
async (file) => {
|
|
14052
|
+
if (!file || !parseSpreadsheet) return;
|
|
14053
|
+
setUploadError(null);
|
|
14054
|
+
setMissing([]);
|
|
14055
|
+
if (file.size > maxUploadBytes) {
|
|
14056
|
+
setUploadError(`File too large (max ${Math.round(maxUploadBytes / 1024 / 1024)} MB)`);
|
|
14057
|
+
return;
|
|
14058
|
+
}
|
|
14059
|
+
setUploading(true);
|
|
14060
|
+
try {
|
|
14061
|
+
let lines = await parseSpreadsheet(file);
|
|
14062
|
+
lines = lines.slice(0, maxUploadRows).map((l) => ({ code: String(l.code ?? "").trim(), quantity: Math.max(1, parseInt(String(l.quantity), 10) || 1) })).filter((l) => l.code.length > 0);
|
|
14063
|
+
if (!lines.length) {
|
|
14064
|
+
setUploadError("No valid rows found in the file");
|
|
14065
|
+
return;
|
|
14066
|
+
}
|
|
14067
|
+
const resolved = [];
|
|
14068
|
+
const notFound = [];
|
|
14069
|
+
for (const line of lines) {
|
|
14070
|
+
const matches = await searchProducts(line.code);
|
|
14071
|
+
const exact = matches.find((m) => m.sku.toLowerCase() === line.code.toLowerCase()) || matches[0];
|
|
14072
|
+
if (!exact) {
|
|
14073
|
+
notFound.push(line.code);
|
|
14074
|
+
continue;
|
|
14075
|
+
}
|
|
14076
|
+
if (resolved.some((r) => r.productId === exact.productId)) continue;
|
|
14077
|
+
const row = blankRow();
|
|
14078
|
+
resolved.push({
|
|
14079
|
+
...row,
|
|
14080
|
+
code: exact.sku,
|
|
14081
|
+
productId: exact.productId,
|
|
14082
|
+
clusterId: exact.clusterId,
|
|
14083
|
+
name: exact.name,
|
|
14084
|
+
netPrice: exact.netPrice,
|
|
14085
|
+
quantity: Math.max(exact.minQuantity, line.quantity),
|
|
14086
|
+
minQuantity: exact.minQuantity
|
|
14087
|
+
});
|
|
14088
|
+
}
|
|
14089
|
+
if (resolved.length) {
|
|
14090
|
+
setRows([...resolved, blankRow()]);
|
|
14091
|
+
}
|
|
14092
|
+
if (notFound.length) {
|
|
14093
|
+
setMissing(notFound);
|
|
14094
|
+
onMissingCodes?.(notFound);
|
|
14095
|
+
}
|
|
14096
|
+
} catch {
|
|
14097
|
+
setUploadError("Could not read the file");
|
|
14098
|
+
} finally {
|
|
14099
|
+
setUploading(false);
|
|
14100
|
+
if (fileInputRef.current) fileInputRef.current.value = "";
|
|
14101
|
+
}
|
|
14102
|
+
},
|
|
14103
|
+
[parseSpreadsheet, maxUploadBytes, maxUploadRows, searchProducts, onMissingCodes]
|
|
14104
|
+
);
|
|
14105
|
+
const handleSubmit = React6.useCallback(async () => {
|
|
14106
|
+
const lines = rows.filter((r) => r.productId).map((r) => ({ productId: r.productId, quantity: r.quantity, clusterId: r.clusterId, code: r.code }));
|
|
14107
|
+
if (!lines.length) {
|
|
14108
|
+
setNotice(L("noItems", "Add at least one product before submitting"));
|
|
14109
|
+
return;
|
|
14110
|
+
}
|
|
14111
|
+
const res = await submit(lines);
|
|
14112
|
+
if (res.success) {
|
|
14113
|
+
setRows(Array.from({ length: Math.max(1, initialRows) }, blankRow));
|
|
14114
|
+
setMissing([]);
|
|
14115
|
+
setNotice(null);
|
|
14116
|
+
} else {
|
|
14117
|
+
setNotice(res.error ?? "Failed to add items to cart");
|
|
14118
|
+
}
|
|
14119
|
+
}, [rows, submit, initialRows, L]);
|
|
14120
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: className ?? "propeller-quick-order", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col lg:flex-row gap-8", children: [
|
|
14121
|
+
parseSpreadsheet ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full lg:w-1/3", children: [
|
|
14122
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold mb-3 text-foreground", children: L("uploadTitle", "Upload Excel file") }),
|
|
14123
|
+
templateUrl ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
14124
|
+
"a",
|
|
14125
|
+
{
|
|
14126
|
+
href: templateUrl,
|
|
14127
|
+
target: "_blank",
|
|
14128
|
+
rel: "noopener nofollow",
|
|
14129
|
+
className: "text-primary hover:underline text-sm inline-block mb-4",
|
|
14130
|
+
children: L("downloadTemplate", "Download XLSX template")
|
|
14131
|
+
}
|
|
14132
|
+
) : null,
|
|
14133
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mb-3", children: L("uploadHint", "Column A: article no. / SKU \u2014 Column B: quantity. First two rows are ignored.") }),
|
|
14134
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14135
|
+
"input",
|
|
14136
|
+
{
|
|
14137
|
+
ref: fileInputRef,
|
|
14138
|
+
type: "file",
|
|
14139
|
+
accept: ".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel",
|
|
14140
|
+
className: "block w-full text-sm text-muted-foreground file:mr-3 file:py-2 file:px-4 file:rounded file:border-0 file:bg-primary file:text-primary-foreground hover:file:bg-primary/90",
|
|
14141
|
+
onChange: (e) => onFileChosen(e.target.files?.[0]),
|
|
14142
|
+
disabled: uploading
|
|
14143
|
+
}
|
|
14144
|
+
),
|
|
14145
|
+
uploading ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground mt-2", children: L("upload", "Uploading\u2026") }) : null,
|
|
14146
|
+
uploadError ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-destructive mt-2", children: uploadError }) : null
|
|
14147
|
+
] }) : null,
|
|
14148
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
|
|
14149
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold mb-3 text-foreground", children: L("title", "Add your products manually") }),
|
|
14150
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "hidden md:grid grid-cols-12 gap-2 px-2 pb-2 text-xs font-medium text-muted-foreground border-b border-border", children: [
|
|
14151
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-3", children: L("colCode", "Article no. / SKU") }),
|
|
14152
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-3", children: L("colName", "Product name") }),
|
|
14153
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-2", children: L("colPrice", "excl. VAT") }),
|
|
14154
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-1", children: L("colQuantity", "Qty") }),
|
|
14155
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-2 text-right", children: L("colTotal", "Total") }),
|
|
14156
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-1" })
|
|
14157
|
+
] }),
|
|
14158
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-border", children: rows.map((r) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-12 gap-2 items-center py-2 relative", children: [
|
|
14159
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "col-span-12 md:col-span-3 relative", children: [
|
|
14160
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14161
|
+
"input",
|
|
14162
|
+
{
|
|
14163
|
+
type: "text",
|
|
14164
|
+
value: r.code,
|
|
14165
|
+
onChange: (e) => onCodeInput(r.key, e.target.value),
|
|
14166
|
+
readOnly: !!r.productId,
|
|
14167
|
+
placeholder: L("colCode", "Article no. / SKU"),
|
|
14168
|
+
className: "w-full rounded border border-input bg-background px-2 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
14169
|
+
}
|
|
14170
|
+
),
|
|
14171
|
+
(r.searching || r.matches.length > 0 || r.searched) && !r.productId ? /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "absolute z-20 mt-1 w-[320px] max-w-[90vw] bg-card border border-border rounded shadow-lg max-h-72 overflow-auto", children: r.searching ? /* @__PURE__ */ jsxRuntime.jsx("li", { className: "px-3 py-2 text-sm text-muted-foreground", children: "\u2026" }) : r.matches.length ? r.matches.map((m) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14172
|
+
"button",
|
|
14173
|
+
{
|
|
14174
|
+
type: "button",
|
|
14175
|
+
onClick: () => selectMatch(r.key, m),
|
|
14176
|
+
className: "flex items-center gap-2 w-full text-left px-3 py-2 hover:bg-muted text-sm",
|
|
14177
|
+
children: [
|
|
14178
|
+
m.imageUrl ? (
|
|
14179
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
14180
|
+
/* @__PURE__ */ jsxRuntime.jsx("img", { src: m.imageUrl, alt: "", width: 32, height: 32, className: "rounded object-cover" })
|
|
14181
|
+
) : null,
|
|
14182
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex-1", children: [
|
|
14183
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-foreground", children: m.name }),
|
|
14184
|
+
m.sku ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "block text-xs text-muted-foreground", children: [
|
|
14185
|
+
"SKU: ",
|
|
14186
|
+
m.sku
|
|
14187
|
+
] }) : null
|
|
14188
|
+
] })
|
|
14189
|
+
]
|
|
14190
|
+
}
|
|
14191
|
+
) }, `${r.key}-${m.productId}`)) : /* @__PURE__ */ jsxRuntime.jsx("li", { className: "px-3 py-2 text-sm text-muted-foreground", children: L("noResults", "No results found") }) }) : null
|
|
14192
|
+
] }),
|
|
14193
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-6 md:col-span-3", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14194
|
+
"input",
|
|
14195
|
+
{
|
|
14196
|
+
type: "text",
|
|
14197
|
+
value: r.name,
|
|
14198
|
+
disabled: true,
|
|
14199
|
+
className: "w-full rounded border border-input bg-muted/40 px-2 py-1.5 text-sm text-muted-foreground"
|
|
14200
|
+
}
|
|
14201
|
+
) }),
|
|
14202
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-3 md:col-span-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14203
|
+
"input",
|
|
14204
|
+
{
|
|
14205
|
+
type: "text",
|
|
14206
|
+
value: r.productId ? displayPrice(r.netPrice) : "",
|
|
14207
|
+
disabled: true,
|
|
14208
|
+
className: "w-full rounded border border-input bg-muted/40 px-2 py-1.5 text-sm text-muted-foreground"
|
|
14209
|
+
}
|
|
14210
|
+
) }),
|
|
14211
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-3 md:col-span-1", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14212
|
+
"input",
|
|
14213
|
+
{
|
|
14214
|
+
type: "number",
|
|
14215
|
+
min: r.minQuantity,
|
|
14216
|
+
step: 1,
|
|
14217
|
+
value: r.productId ? r.quantity : "",
|
|
14218
|
+
disabled: !r.productId,
|
|
14219
|
+
onChange: (e) => setQuantity(r.key, e.target.value),
|
|
14220
|
+
onKeyDown: (e) => {
|
|
14221
|
+
if (["e", "E", "+", "-", "."].includes(e.key)) e.preventDefault();
|
|
14222
|
+
},
|
|
14223
|
+
className: "w-full rounded border border-input bg-background px-2 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:bg-muted/40"
|
|
14224
|
+
}
|
|
14225
|
+
) }),
|
|
14226
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-9 md:col-span-2 text-right text-sm text-foreground whitespace-nowrap", children: r.productId ? displayPrice(r.netPrice * r.quantity) : "" }),
|
|
14227
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-3 md:col-span-1 flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14228
|
+
"button",
|
|
14229
|
+
{
|
|
14230
|
+
type: "button",
|
|
14231
|
+
onClick: () => removeRow(r.key),
|
|
14232
|
+
"aria-label": L("remove", "Remove"),
|
|
14233
|
+
className: "text-muted-foreground hover:text-destructive p-1",
|
|
14234
|
+
children: "\u2715"
|
|
14235
|
+
}
|
|
14236
|
+
) })
|
|
14237
|
+
] }, r.key)) }),
|
|
14238
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
14239
|
+
"button",
|
|
14240
|
+
{
|
|
14241
|
+
type: "button",
|
|
14242
|
+
onClick: addRow,
|
|
14243
|
+
className: "mt-3 text-primary hover:underline text-sm font-medium",
|
|
14244
|
+
children: [
|
|
14245
|
+
"+ ",
|
|
14246
|
+
L("addRow", "Add more rows")
|
|
14247
|
+
]
|
|
14248
|
+
}
|
|
14249
|
+
),
|
|
14250
|
+
missing.length ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-destructive mt-4", children: [
|
|
14251
|
+
L("missingCodes", "The following products were not added:"),
|
|
14252
|
+
" ",
|
|
14253
|
+
missing.join(", ")
|
|
14254
|
+
] }) : null,
|
|
14255
|
+
notice ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-destructive mt-2", children: notice }) : null,
|
|
14256
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-end mt-6 pt-4 border-t border-border", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14257
|
+
"button",
|
|
14258
|
+
{
|
|
14259
|
+
type: "button",
|
|
14260
|
+
onClick: handleSubmit,
|
|
14261
|
+
disabled: submitting || resolvedCount === 0,
|
|
14262
|
+
className: "bg-primary text-primary-foreground px-6 py-2.5 rounded font-medium hover:bg-primary/90 transition disabled:opacity-50 disabled:cursor-not-allowed",
|
|
14263
|
+
children: submitting ? L("adding", "Adding\u2026") : L("addToCart", "Add to cart")
|
|
14264
|
+
}
|
|
14265
|
+
) })
|
|
14266
|
+
] })
|
|
14267
|
+
] }) });
|
|
14268
|
+
}
|
|
14269
|
+
function QuickOrder(rawProps) {
|
|
14270
|
+
const props = useInfraProps(rawProps);
|
|
14271
|
+
return /* @__PURE__ */ jsxRuntime.jsx(QuickOrderInner, { ...props });
|
|
14272
|
+
}
|
|
13798
14273
|
function OrderShipments(props) {
|
|
13799
14274
|
const [activeShipment, setActiveShipment] = React6.useState(null);
|
|
13800
14275
|
const shipments = props.order?.shipments || [];
|
|
@@ -18271,6 +18746,7 @@ exports.PropellerDepsProvider = PropellerDepsProvider;
|
|
|
18271
18746
|
exports.PropellerProvider = PropellerProvider;
|
|
18272
18747
|
exports.PurchaseAuthorizationConfigurator = PurchaseAuthorizationConfigurator_default;
|
|
18273
18748
|
exports.PurchaseAuthorizationRequests = PurchaseAuthorizationRequests_default;
|
|
18749
|
+
exports.QuickOrder = QuickOrder;
|
|
18274
18750
|
exports.QuoteActions = QuoteActions_default;
|
|
18275
18751
|
exports.RegisterForm = RegisterForm_default;
|
|
18276
18752
|
exports.SearchBar = SearchBar_default;
|
|
@@ -18301,6 +18777,7 @@ exports.usePropellerDeps = usePropellerDeps;
|
|
|
18301
18777
|
exports.usePropellerScope = usePropellerScope;
|
|
18302
18778
|
exports.usePurchaseAuthorizationConfigurator = usePurchaseAuthorizationConfigurator;
|
|
18303
18779
|
exports.usePurchaseAuthorizationRequests = usePurchaseAuthorizationRequests;
|
|
18780
|
+
exports.useQuickOrder = useQuickOrder;
|
|
18304
18781
|
exports.useRequiredPropellerDeps = useRequiredPropellerDeps;
|
|
18305
18782
|
exports.useResolvedProps = useResolvedProps;
|
|
18306
18783
|
exports.useServices = useServices;
|