@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/dist/index.js CHANGED
@@ -2478,6 +2478,140 @@ function useProductSearch(options) {
2478
2478
  goToPage: pagination.goToPage
2479
2479
  };
2480
2480
  }
2481
+ function toMatch(item, language) {
2482
+ const isCluster = "clusterId" in item;
2483
+ const displayItem = isCluster ? item.defaultProduct : item;
2484
+ const productId = displayItem?.productId ?? item.productId;
2485
+ const clusterId = isCluster ? item.clusterId : void 0;
2486
+ const name = language && item.names?.find((n) => n.language === language)?.value || item.names?.[0]?.value || "Product";
2487
+ const netPrice = displayItem?.price?.net ?? 0;
2488
+ const grossPrice = displayItem?.price?.gross ?? 0;
2489
+ const minQuantity = Math.max(1, displayItem?.minimumQuantity ?? 1);
2490
+ const imageUrl = isCluster ? getClusterImageUrl(item) : getProductImageUrl(item);
2491
+ return {
2492
+ productId,
2493
+ clusterId,
2494
+ name,
2495
+ sku: item.sku || displayItem?.sku || "",
2496
+ netPrice,
2497
+ grossPrice,
2498
+ minQuantity,
2499
+ imageUrl
2500
+ };
2501
+ }
2502
+ function useQuickOrder(options) {
2503
+ const {
2504
+ graphqlClient,
2505
+ user,
2506
+ companyId,
2507
+ configuration = {},
2508
+ onCartCreated,
2509
+ afterAddToCart
2510
+ } = options;
2511
+ const language = options.language || "NL";
2512
+ const searchLimit = options.searchLimit ?? 8;
2513
+ const [submitting, setSubmitting] = useState(false);
2514
+ const [error, setError] = useState(null);
2515
+ const searchProducts = useCallback(
2516
+ async (term) => {
2517
+ const trimmed = term.trim();
2518
+ if (!trimmed || !graphqlClient) return [];
2519
+ try {
2520
+ const service = createServices(graphqlClient).product;
2521
+ const input = {
2522
+ term: trimmed,
2523
+ language,
2524
+ page: 1,
2525
+ offset: searchLimit,
2526
+ statuses: [ProductStatus.A, ProductStatus.P, ProductStatus.T, ProductStatus.S],
2527
+ sortInputs: [{ field: ProductSortField.RELEVANCE, order: SortOrder.DESC }],
2528
+ searchFields: [
2529
+ {
2530
+ fieldNames: [
2531
+ ProductSearchableField.SKU,
2532
+ ProductSearchableField.NAME,
2533
+ ProductSearchableField.KEYWORDS,
2534
+ ProductSearchableField.CUSTOM_KEYWORDS
2535
+ ],
2536
+ boost: 5
2537
+ },
2538
+ {
2539
+ fieldNames: [
2540
+ ProductSearchableField.MANUFACTURER_CODE,
2541
+ ProductSearchableField.EAN_CODE,
2542
+ ProductSearchableField.BAR_CODE,
2543
+ ProductSearchableField.SUPPLIER_CODE,
2544
+ ProductSearchableField.PRODUCT_ID
2545
+ ],
2546
+ boost: 1
2547
+ }
2548
+ ]
2549
+ };
2550
+ const variables = {
2551
+ input,
2552
+ language,
2553
+ imageVariantFilters: configuration.imageVariantFiltersSmall
2554
+ };
2555
+ const result = await service.getProducts(variables);
2556
+ const items = result?.items ?? [];
2557
+ return items.map((it) => toMatch(it, language));
2558
+ } catch {
2559
+ return [];
2560
+ }
2561
+ },
2562
+ [graphqlClient, language, searchLimit, configuration]
2563
+ );
2564
+ const submit = useCallback(
2565
+ async (lines) => {
2566
+ const valid = lines.filter((l) => l.productId && (l.quantity ?? 0) > 0);
2567
+ if (!valid.length) return { success: false, error: "No items to add" };
2568
+ if (!graphqlClient) return { success: false, error: "No GraphQL client" };
2569
+ setSubmitting(true);
2570
+ setError(null);
2571
+ try {
2572
+ const services = createServices(graphqlClient);
2573
+ const cart = await initCart({
2574
+ services,
2575
+ user: user ?? null,
2576
+ companyId,
2577
+ language,
2578
+ imageSearchFilters: configuration.imageSearchFiltersGrid,
2579
+ imageVariantFilters: configuration.imageVariantFiltersSmall,
2580
+ onCartCreated
2581
+ });
2582
+ const items = valid.map((l) => ({
2583
+ productId: l.productId,
2584
+ quantity: l.quantity,
2585
+ ...l.clusterId ? { clusterId: l.clusterId } : {}
2586
+ }));
2587
+ const bulk = await services.cart.bulkUpdateCartItems({
2588
+ input: { cartId: cart.cartId, items }
2589
+ });
2590
+ const updated = await services.cart.getCart({
2591
+ cartId: cart.cartId,
2592
+ language,
2593
+ imageSearchFilters: configuration.imageSearchFiltersGrid,
2594
+ imageVariantFilters: configuration.imageVariantFiltersSmall
2595
+ });
2596
+ const finalCart = updated ?? cart;
2597
+ afterAddToCart?.(finalCart);
2598
+ return {
2599
+ success: true,
2600
+ cart: finalCart,
2601
+ added: (bulk?.created ?? 0) + (bulk?.updated ?? 0)
2602
+ };
2603
+ } catch (e) {
2604
+ const msg = e instanceof Error ? e.message : "Failed to add items to cart";
2605
+ setError(msg);
2606
+ return { success: false, error: msg };
2607
+ } finally {
2608
+ setSubmitting(false);
2609
+ }
2610
+ },
2611
+ [graphqlClient, user, companyId, language, configuration, onCartCreated, afterAddToCart]
2612
+ );
2613
+ return { submitting, error, searchProducts, submit };
2614
+ }
2481
2615
  var inflightFetches3 = /* @__PURE__ */ new Set();
2482
2616
  var STOREFRONT_STATUSES = [
2483
2617
  ProductStatus.A,
@@ -6365,7 +6499,7 @@ function CartItem(rawProps) {
6365
6499
  }
6366
6500
  return "#";
6367
6501
  }
6368
- function getProductImageUrl5() {
6502
+ function getProductImageUrl6() {
6369
6503
  return props.cartItem.product?.media?.images?.items?.[0]?.imageVariants?.[0]?.url || "";
6370
6504
  }
6371
6505
  function getProductSku3() {
@@ -6549,7 +6683,7 @@ function CartItem(rawProps) {
6549
6683
  derived: {
6550
6684
  name: getProductName5(),
6551
6685
  sku: getProductSku3(),
6552
- imageUrl: getProductImageUrl5(),
6686
+ imageUrl: getProductImageUrl6(),
6553
6687
  productUrl: getProductUrl2(),
6554
6688
  isBundle: isBundleItem(),
6555
6689
  bundleName: getBundleName(),
@@ -9523,7 +9657,7 @@ function GridToolbar(rawProps) {
9523
9657
  const [currentSortOrder, setCurrentSortOrder] = useState(SortOrder.DESC);
9524
9658
  const [currentOffset, setCurrentOffset] = useState(12);
9525
9659
  const [currentViewMode, setCurrentViewMode] = useState("grid");
9526
- function getLabel54(key) {
9660
+ function getLabel55(key) {
9527
9661
  const labels = props.labels || {};
9528
9662
  return labels[key] !== void 0 ? labels[key] : DEFAULT_LABELS[key] || key;
9529
9663
  }
@@ -9602,7 +9736,7 @@ function GridToolbar(rawProps) {
9602
9736
  /* @__PURE__ */ jsx("div", { className: "propeller-grid-toolbar__count text-sm text-muted-foreground font-medium", children: props.itemsFound > 0 ? /* @__PURE__ */ jsxs("span", { children: [
9603
9737
  props.itemsFound,
9604
9738
  "\xA0",
9605
- props.itemsFound === 1 ? getLabel54("productSingular") : getLabel54("productPlural")
9739
+ props.itemsFound === 1 ? getLabel55("productSingular") : getLabel55("productPlural")
9606
9740
  ] }) : null }),
9607
9741
  /* @__PURE__ */ jsxs("div", { className: "propeller-grid-toolbar__controls flex flex-wrap items-center gap-3", children: [
9608
9742
  /* @__PURE__ */ jsx(
@@ -9613,7 +9747,7 @@ function GridToolbar(rawProps) {
9613
9747
  onChange: (e) => handleOffsetChange(parseInt(e.target.value)),
9614
9748
  children: getOffsetOptions()?.map((n) => /* @__PURE__ */ jsxs("option", { value: n, children: [
9615
9749
  n,
9616
- getLabel54("perPage")
9750
+ getLabel55("perPage")
9617
9751
  ] }, n))
9618
9752
  }
9619
9753
  ),
@@ -9629,7 +9763,7 @@ function GridToolbar(rawProps) {
9629
9763
  {
9630
9764
  value: field,
9631
9765
  disabled: field === "PRICE" && isPriceSortDisabled(),
9632
- children: getLabel54(field)
9766
+ children: getLabel55(field)
9633
9767
  },
9634
9768
  field
9635
9769
  ))
@@ -9642,8 +9776,8 @@ function GridToolbar(rawProps) {
9642
9776
  value: currentSortOrder,
9643
9777
  onChange: (e) => handleSortOrderChange(e.target.value),
9644
9778
  children: [
9645
- /* @__PURE__ */ jsx("option", { value: SortOrder.ASC, children: getLabel54("ASC") }),
9646
- /* @__PURE__ */ jsx("option", { value: SortOrder.DESC, children: getLabel54("DESC") })
9779
+ /* @__PURE__ */ jsx("option", { value: SortOrder.ASC, children: getLabel55("ASC") }),
9780
+ /* @__PURE__ */ jsx("option", { value: SortOrder.DESC, children: getLabel55("DESC") })
9647
9781
  ]
9648
9782
  }
9649
9783
  ),
@@ -9653,7 +9787,7 @@ function GridToolbar(rawProps) {
9653
9787
  type: "button",
9654
9788
  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",
9655
9789
  onClick: (event) => handleViewChange(),
9656
- title: currentViewMode === "grid" ? getLabel54("switchToList") : getLabel54("switchToGrid"),
9790
+ title: currentViewMode === "grid" ? getLabel55("switchToList") : getLabel55("switchToGrid"),
9657
9791
  children: [
9658
9792
  currentViewMode === "grid" ? /* @__PURE__ */ jsxs(
9659
9793
  "svg",
@@ -9711,7 +9845,7 @@ function GridToolbar(rawProps) {
9711
9845
  onClick: (event) => {
9712
9846
  if (props.onClearFilters) props.onClearFilters();
9713
9847
  },
9714
- children: getLabel54("clearAll")
9848
+ children: getLabel55("clearAll")
9715
9849
  }
9716
9850
  ),
9717
9851
  props.priceFilterMin !== void 0 || props.priceFilterMax !== void 0 ? /* @__PURE__ */ jsxs(
@@ -9722,7 +9856,7 @@ function GridToolbar(rawProps) {
9722
9856
  if (props.onPriceFilterRemove) props.onPriceFilterRemove();
9723
9857
  },
9724
9858
  children: [
9725
- getLabel54("price"),
9859
+ getLabel55("price"),
9726
9860
  ": \u20AC",
9727
9861
  props.priceFilterMin ?? 0,
9728
9862
  " \u2013 \u20AC",
@@ -9759,7 +9893,7 @@ var DEFAULT_LABELS2 = {
9759
9893
  of: "of"
9760
9894
  };
9761
9895
  function GridPagination(props) {
9762
- function getLabel54(key) {
9896
+ function getLabel55(key) {
9763
9897
  const labels = props.labels || {};
9764
9898
  return labels[key] !== void 0 ? labels[key] : DEFAULT_LABELS2[key] || key;
9765
9899
  }
@@ -9832,15 +9966,15 @@ function GridPagination(props) {
9832
9966
  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",
9833
9967
  disabled: currentPage === 1,
9834
9968
  onClick: () => handlePageChange(currentPage - 1),
9835
- children: getLabel54("previous")
9969
+ children: getLabel55("previous")
9836
9970
  }
9837
9971
  ),
9838
9972
  /* @__PURE__ */ jsxs("span", { className: "propeller-grid-pagination__info px-2 text-sm font-medium text-muted-foreground", children: [
9839
- getLabel54("page"),
9973
+ getLabel55("page"),
9840
9974
  "\xA0",
9841
9975
  currentPage,
9842
9976
  "\xA0",
9843
- getLabel54("of"),
9977
+ getLabel55("of"),
9844
9978
  "\xA0",
9845
9979
  totalPages
9846
9980
  ] }),
@@ -9851,7 +9985,7 @@ function GridPagination(props) {
9851
9985
  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",
9852
9986
  disabled: currentPage === totalPages,
9853
9987
  onClick: () => handlePageChange(currentPage + 1),
9854
- children: getLabel54("next")
9988
+ children: getLabel55("next")
9855
9989
  }
9856
9990
  )
9857
9991
  ] }) : null,
@@ -9863,7 +9997,7 @@ function GridPagination(props) {
9863
9997
  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",
9864
9998
  disabled: currentPage === 1,
9865
9999
  onClick: () => handlePageChange(currentPage - 1),
9866
- children: getLabel54("previous")
10000
+ children: getLabel55("previous")
9867
10001
  }
9868
10002
  ),
9869
10003
  getFullPages().map((item, idx) => /* @__PURE__ */ jsx(
@@ -9890,7 +10024,7 @@ function GridPagination(props) {
9890
10024
  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",
9891
10025
  disabled: currentPage === totalPages,
9892
10026
  onClick: () => handlePageChange(currentPage + 1),
9893
- children: getLabel54("next")
10027
+ children: getLabel55("next")
9894
10028
  }
9895
10029
  )
9896
10030
  ] }) : null
@@ -10369,7 +10503,7 @@ function formatPrice9(price, currency) {
10369
10503
  function getProductName2(product) {
10370
10504
  return product.names?.[0]?.value || `Product ${product.productId}`;
10371
10505
  }
10372
- function getProductImageUrl3(product) {
10506
+ function getProductImageUrl4(product) {
10373
10507
  const media = product.media;
10374
10508
  if (media?.images?.items && Array.isArray(media.images.items) && media.images.items.length > 0) {
10375
10509
  const firstImage = media.images.items[0];
@@ -10398,7 +10532,7 @@ function ClusterOptions(props) {
10398
10532
  (p) => p.productId.toString() === selectedProductId
10399
10533
  );
10400
10534
  if (selectedProduct) {
10401
- previewImageUrl = getProductImageUrl3(selectedProduct);
10535
+ previewImageUrl = getProductImageUrl4(selectedProduct);
10402
10536
  previewName = getProductName2(selectedProduct);
10403
10537
  previewPrice = formatPrice9(selectedProduct.price?.gross || 0, props.currency ?? "\u20AC");
10404
10538
  }
@@ -13775,6 +13909,347 @@ function OrderList(rawProps) {
13775
13909
  ] });
13776
13910
  }
13777
13911
  var OrderList_default = OrderList;
13912
+ var ROW_SEQ = 0;
13913
+ function blankRow() {
13914
+ ROW_SEQ += 1;
13915
+ return {
13916
+ key: `qo-${ROW_SEQ}`,
13917
+ code: "",
13918
+ productId: null,
13919
+ name: "",
13920
+ netPrice: 0,
13921
+ quantity: 1,
13922
+ minQuantity: 1,
13923
+ matches: [],
13924
+ searching: false,
13925
+ searched: false
13926
+ };
13927
+ }
13928
+ function QuickOrderInner(props) {
13929
+ const {
13930
+ companyId,
13931
+ language = "NL",
13932
+ currency = "\u20AC",
13933
+ configuration,
13934
+ initialRows = 5,
13935
+ searchThreshold = 3,
13936
+ debounceMs = 300,
13937
+ parseSpreadsheet,
13938
+ templateUrl,
13939
+ maxUploadBytes = 2 * 1024 * 1024,
13940
+ maxUploadRows = 500,
13941
+ onCartCreated,
13942
+ afterAddToCart,
13943
+ onMissingCodes,
13944
+ className,
13945
+ labels
13946
+ } = props;
13947
+ const user = props.user ?? null;
13948
+ const graphqlClient = props.graphqlClient;
13949
+ const displayPrice = (n) => props.formatPrice ? props.formatPrice(n) : formatPrice(n, { symbol: currency });
13950
+ const L = (key, fallback) => getLabel(labels, key, fallback);
13951
+ const { submitting, searchProducts, submit } = useQuickOrder({
13952
+ graphqlClient,
13953
+ user,
13954
+ companyId,
13955
+ language,
13956
+ configuration,
13957
+ onCartCreated,
13958
+ afterAddToCart
13959
+ });
13960
+ const [rows, setRows] = useState(
13961
+ () => Array.from({ length: Math.max(1, initialRows) }, blankRow)
13962
+ );
13963
+ const [missing, setMissing] = useState([]);
13964
+ const [uploadError, setUploadError] = useState(null);
13965
+ const [uploading, setUploading] = useState(false);
13966
+ const [notice, setNotice] = useState(null);
13967
+ const fileInputRef = useRef(null);
13968
+ const searchTimers = useRef({});
13969
+ const patchRow = useCallback((key, patch) => {
13970
+ setRows((prev) => prev.map((r) => r.key === key ? { ...r, ...patch } : r));
13971
+ }, []);
13972
+ const onCodeInput = useCallback(
13973
+ (key, value) => {
13974
+ patchRow(key, { code: value, productId: null, name: "", netPrice: 0, searched: false });
13975
+ setNotice(null);
13976
+ const timers = searchTimers.current;
13977
+ if (timers[key]) clearTimeout(timers[key]);
13978
+ if (value.trim().length < searchThreshold) {
13979
+ patchRow(key, { matches: [], searching: false, searched: false });
13980
+ return;
13981
+ }
13982
+ patchRow(key, { searching: true });
13983
+ timers[key] = setTimeout(async () => {
13984
+ const results = await searchProducts(value);
13985
+ patchRow(key, { matches: results, searching: false, searched: true });
13986
+ }, debounceMs);
13987
+ },
13988
+ [patchRow, searchProducts, searchThreshold, debounceMs]
13989
+ );
13990
+ const selectMatch = useCallback(
13991
+ (key, match) => {
13992
+ const dup = rows.some((r) => r.key !== key && r.productId && r.code === match.sku);
13993
+ if (dup) {
13994
+ setNotice(L("alreadyInList", "Product is already in the list"));
13995
+ patchRow(key, { code: "", matches: [], productId: null, name: "", netPrice: 0, searched: false });
13996
+ return;
13997
+ }
13998
+ patchRow(key, {
13999
+ code: match.sku,
14000
+ productId: match.productId,
14001
+ clusterId: match.clusterId,
14002
+ name: match.name,
14003
+ netPrice: match.netPrice,
14004
+ quantity: match.minQuantity,
14005
+ minQuantity: match.minQuantity,
14006
+ matches: [],
14007
+ searching: false,
14008
+ searched: false
14009
+ });
14010
+ },
14011
+ [rows, patchRow, L]
14012
+ );
14013
+ const setQuantity = useCallback(
14014
+ (key, raw) => {
14015
+ const n = parseInt(raw, 10);
14016
+ setRows(
14017
+ (prev) => prev.map(
14018
+ (r) => r.key === key ? { ...r, quantity: Number.isFinite(n) && n > 0 ? Math.max(r.minQuantity, n) : r.minQuantity } : r
14019
+ )
14020
+ );
14021
+ },
14022
+ []
14023
+ );
14024
+ const addRow = useCallback(() => setRows((prev) => [...prev, blankRow()]), []);
14025
+ const removeRow = useCallback(
14026
+ (key) => setRows((prev) => prev.length > 1 ? prev.filter((r) => r.key !== key) : prev),
14027
+ []
14028
+ );
14029
+ const resolvedCount = useMemo(() => rows.filter((r) => r.productId).length, [rows]);
14030
+ const onFileChosen = useCallback(
14031
+ async (file) => {
14032
+ if (!file || !parseSpreadsheet) return;
14033
+ setUploadError(null);
14034
+ setMissing([]);
14035
+ if (file.size > maxUploadBytes) {
14036
+ setUploadError(`File too large (max ${Math.round(maxUploadBytes / 1024 / 1024)} MB)`);
14037
+ return;
14038
+ }
14039
+ setUploading(true);
14040
+ try {
14041
+ let lines = await parseSpreadsheet(file);
14042
+ 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);
14043
+ if (!lines.length) {
14044
+ setUploadError("No valid rows found in the file");
14045
+ return;
14046
+ }
14047
+ const resolved = [];
14048
+ const notFound = [];
14049
+ for (const line of lines) {
14050
+ const matches = await searchProducts(line.code);
14051
+ const exact = matches.find((m) => m.sku.toLowerCase() === line.code.toLowerCase()) || matches[0];
14052
+ if (!exact) {
14053
+ notFound.push(line.code);
14054
+ continue;
14055
+ }
14056
+ if (resolved.some((r) => r.productId === exact.productId)) continue;
14057
+ const row = blankRow();
14058
+ resolved.push({
14059
+ ...row,
14060
+ code: exact.sku,
14061
+ productId: exact.productId,
14062
+ clusterId: exact.clusterId,
14063
+ name: exact.name,
14064
+ netPrice: exact.netPrice,
14065
+ quantity: Math.max(exact.minQuantity, line.quantity),
14066
+ minQuantity: exact.minQuantity
14067
+ });
14068
+ }
14069
+ if (resolved.length) {
14070
+ setRows([...resolved, blankRow()]);
14071
+ }
14072
+ if (notFound.length) {
14073
+ setMissing(notFound);
14074
+ onMissingCodes?.(notFound);
14075
+ }
14076
+ } catch {
14077
+ setUploadError("Could not read the file");
14078
+ } finally {
14079
+ setUploading(false);
14080
+ if (fileInputRef.current) fileInputRef.current.value = "";
14081
+ }
14082
+ },
14083
+ [parseSpreadsheet, maxUploadBytes, maxUploadRows, searchProducts, onMissingCodes]
14084
+ );
14085
+ const handleSubmit = useCallback(async () => {
14086
+ const lines = rows.filter((r) => r.productId).map((r) => ({ productId: r.productId, quantity: r.quantity, clusterId: r.clusterId, code: r.code }));
14087
+ if (!lines.length) {
14088
+ setNotice(L("noItems", "Add at least one product before submitting"));
14089
+ return;
14090
+ }
14091
+ const res = await submit(lines);
14092
+ if (res.success) {
14093
+ setRows(Array.from({ length: Math.max(1, initialRows) }, blankRow));
14094
+ setMissing([]);
14095
+ setNotice(null);
14096
+ } else {
14097
+ setNotice(res.error ?? "Failed to add items to cart");
14098
+ }
14099
+ }, [rows, submit, initialRows, L]);
14100
+ return /* @__PURE__ */ jsx("div", { className: className ?? "propeller-quick-order", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col lg:flex-row gap-8", children: [
14101
+ parseSpreadsheet ? /* @__PURE__ */ jsxs("div", { className: "w-full lg:w-1/3", children: [
14102
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold mb-3 text-foreground", children: L("uploadTitle", "Upload Excel file") }),
14103
+ templateUrl ? /* @__PURE__ */ jsx(
14104
+ "a",
14105
+ {
14106
+ href: templateUrl,
14107
+ target: "_blank",
14108
+ rel: "noopener nofollow",
14109
+ className: "text-primary hover:underline text-sm inline-block mb-4",
14110
+ children: L("downloadTemplate", "Download XLSX template")
14111
+ }
14112
+ ) : null,
14113
+ /* @__PURE__ */ 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.") }),
14114
+ /* @__PURE__ */ jsx(
14115
+ "input",
14116
+ {
14117
+ ref: fileInputRef,
14118
+ type: "file",
14119
+ accept: ".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel",
14120
+ 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",
14121
+ onChange: (e) => onFileChosen(e.target.files?.[0]),
14122
+ disabled: uploading
14123
+ }
14124
+ ),
14125
+ uploading ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-2", children: L("upload", "Uploading\u2026") }) : null,
14126
+ uploadError ? /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive mt-2", children: uploadError }) : null
14127
+ ] }) : null,
14128
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
14129
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold mb-3 text-foreground", children: L("title", "Add your products manually") }),
14130
+ /* @__PURE__ */ 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: [
14131
+ /* @__PURE__ */ jsx("div", { className: "col-span-3", children: L("colCode", "Article no. / SKU") }),
14132
+ /* @__PURE__ */ jsx("div", { className: "col-span-3", children: L("colName", "Product name") }),
14133
+ /* @__PURE__ */ jsx("div", { className: "col-span-2", children: L("colPrice", "excl. VAT") }),
14134
+ /* @__PURE__ */ jsx("div", { className: "col-span-1", children: L("colQuantity", "Qty") }),
14135
+ /* @__PURE__ */ jsx("div", { className: "col-span-2 text-right", children: L("colTotal", "Total") }),
14136
+ /* @__PURE__ */ jsx("div", { className: "col-span-1" })
14137
+ ] }),
14138
+ /* @__PURE__ */ jsx("div", { className: "divide-y divide-border", children: rows.map((r) => /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-12 gap-2 items-center py-2 relative", children: [
14139
+ /* @__PURE__ */ jsxs("div", { className: "col-span-12 md:col-span-3 relative", children: [
14140
+ /* @__PURE__ */ jsx(
14141
+ "input",
14142
+ {
14143
+ type: "text",
14144
+ value: r.code,
14145
+ onChange: (e) => onCodeInput(r.key, e.target.value),
14146
+ readOnly: !!r.productId,
14147
+ placeholder: L("colCode", "Article no. / SKU"),
14148
+ 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"
14149
+ }
14150
+ ),
14151
+ (r.searching || r.matches.length > 0 || r.searched) && !r.productId ? /* @__PURE__ */ 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__ */ jsx("li", { className: "px-3 py-2 text-sm text-muted-foreground", children: "\u2026" }) : r.matches.length ? r.matches.map((m) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
14152
+ "button",
14153
+ {
14154
+ type: "button",
14155
+ onClick: () => selectMatch(r.key, m),
14156
+ className: "flex items-center gap-2 w-full text-left px-3 py-2 hover:bg-muted text-sm",
14157
+ children: [
14158
+ m.imageUrl ? (
14159
+ // eslint-disable-next-line @next/next/no-img-element
14160
+ /* @__PURE__ */ jsx("img", { src: m.imageUrl, alt: "", width: 32, height: 32, className: "rounded object-cover" })
14161
+ ) : null,
14162
+ /* @__PURE__ */ jsxs("span", { className: "flex-1", children: [
14163
+ /* @__PURE__ */ jsx("span", { className: "block text-foreground", children: m.name }),
14164
+ m.sku ? /* @__PURE__ */ jsxs("span", { className: "block text-xs text-muted-foreground", children: [
14165
+ "SKU: ",
14166
+ m.sku
14167
+ ] }) : null
14168
+ ] })
14169
+ ]
14170
+ }
14171
+ ) }, `${r.key}-${m.productId}`)) : /* @__PURE__ */ jsx("li", { className: "px-3 py-2 text-sm text-muted-foreground", children: L("noResults", "No results found") }) }) : null
14172
+ ] }),
14173
+ /* @__PURE__ */ jsx("div", { className: "col-span-6 md:col-span-3", children: /* @__PURE__ */ jsx(
14174
+ "input",
14175
+ {
14176
+ type: "text",
14177
+ value: r.name,
14178
+ disabled: true,
14179
+ className: "w-full rounded border border-input bg-muted/40 px-2 py-1.5 text-sm text-muted-foreground"
14180
+ }
14181
+ ) }),
14182
+ /* @__PURE__ */ jsx("div", { className: "col-span-3 md:col-span-2", children: /* @__PURE__ */ jsx(
14183
+ "input",
14184
+ {
14185
+ type: "text",
14186
+ value: r.productId ? displayPrice(r.netPrice) : "",
14187
+ disabled: true,
14188
+ className: "w-full rounded border border-input bg-muted/40 px-2 py-1.5 text-sm text-muted-foreground"
14189
+ }
14190
+ ) }),
14191
+ /* @__PURE__ */ jsx("div", { className: "col-span-3 md:col-span-1", children: /* @__PURE__ */ jsx(
14192
+ "input",
14193
+ {
14194
+ type: "number",
14195
+ min: r.minQuantity,
14196
+ step: 1,
14197
+ value: r.productId ? r.quantity : "",
14198
+ disabled: !r.productId,
14199
+ onChange: (e) => setQuantity(r.key, e.target.value),
14200
+ onKeyDown: (e) => {
14201
+ if (["e", "E", "+", "-", "."].includes(e.key)) e.preventDefault();
14202
+ },
14203
+ 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"
14204
+ }
14205
+ ) }),
14206
+ /* @__PURE__ */ 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) : "" }),
14207
+ /* @__PURE__ */ jsx("div", { className: "col-span-3 md:col-span-1 flex justify-end", children: /* @__PURE__ */ jsx(
14208
+ "button",
14209
+ {
14210
+ type: "button",
14211
+ onClick: () => removeRow(r.key),
14212
+ "aria-label": L("remove", "Remove"),
14213
+ className: "text-muted-foreground hover:text-destructive p-1",
14214
+ children: "\u2715"
14215
+ }
14216
+ ) })
14217
+ ] }, r.key)) }),
14218
+ /* @__PURE__ */ jsxs(
14219
+ "button",
14220
+ {
14221
+ type: "button",
14222
+ onClick: addRow,
14223
+ className: "mt-3 text-primary hover:underline text-sm font-medium",
14224
+ children: [
14225
+ "+ ",
14226
+ L("addRow", "Add more rows")
14227
+ ]
14228
+ }
14229
+ ),
14230
+ missing.length ? /* @__PURE__ */ jsxs("p", { className: "text-sm text-destructive mt-4", children: [
14231
+ L("missingCodes", "The following products were not added:"),
14232
+ " ",
14233
+ missing.join(", ")
14234
+ ] }) : null,
14235
+ notice ? /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive mt-2", children: notice }) : null,
14236
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end mt-6 pt-4 border-t border-border", children: /* @__PURE__ */ jsx(
14237
+ "button",
14238
+ {
14239
+ type: "button",
14240
+ onClick: handleSubmit,
14241
+ disabled: submitting || resolvedCount === 0,
14242
+ 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",
14243
+ children: submitting ? L("adding", "Adding\u2026") : L("addToCart", "Add to cart")
14244
+ }
14245
+ ) })
14246
+ ] })
14247
+ ] }) });
14248
+ }
14249
+ function QuickOrder(rawProps) {
14250
+ const props = useInfraProps(rawProps);
14251
+ return /* @__PURE__ */ jsx(QuickOrderInner, { ...props });
14252
+ }
13778
14253
  function OrderShipments(props) {
13779
14254
  const [activeShipment, setActiveShipment] = useState(null);
13780
14255
  const shipments = props.order?.shipments || [];
@@ -18178,6 +18653,6 @@ function UserDetails(rawProps) {
18178
18653
  }
18179
18654
  var UserDetails_default = UserDetails;
18180
18655
 
18181
- export { AccountIconAndMenu_default as AccountIconAndMenu, ActionCode_default as ActionCode, AddToCart_default as AddToCart, AddToFavorite_default as AddToFavorite, AddressCard_default as AddressCard, AddressSelector_default as AddressSelector, BreadcrumbsWithProvider_default as Breadcrumbs, CartBonusItems_default as CartBonusItems, CartCarriers_default as CartCarriers, CartIconAndSidebar_default as CartIconAndSidebar, CartItem_default as CartItem, CartOverview_default as CartOverview, CartPaymethods_default as CartPaymethods, CartSummary_default as CartSummary, CategoryDescription_default as CategoryDescription, CategoryShortDescriptionWithProvider_default as CategoryShortDescription, ClusterCard_default as ClusterCard, ClusterConfigurator_default as ClusterConfigurator, ClusterInfo_default as ClusterInfo, ClusterOptions_default as ClusterOptions, CompanySwitcher_default as CompanySwitcher, AddToCart_default as DefaultAddToCart, AddToFavorite_default as DefaultAddToFavorite, ItemStock_default as DefaultItemStock, DefaultProductBadges, ProductBulkPrices_default as DefaultProductBulkPrices, ProductBundles_default as DefaultProductBundles, DefaultProductImage, ProductPrice_default as DefaultProductPrice, DefaultProductSurcharges, DeliveryDate_default as DeliveryDate, FavoriteListDetails_default as FavoriteListDetails, FavoriteListItem_default as FavoriteListItem, FavoriteLists_default as FavoriteLists, ForgotPassword_default as ForgotPassword, GridFilters_default as GridFilters, GridFiltersPanel, GridPagination_default as GridPagination, GridTitleWithProvider_default as GridTitle, GridToolbar_default as GridToolbar, ItemStock_default as ItemStock, ItemsOverview_default as ItemsOverview, LoginForm_default as LoginForm, MachineCard, MachineGrid, Menu_default as Menu, OrderActions_default as OrderActions, OrderBonusItems_default as OrderBonusItems, OrderItemCard_default as OrderItemCard, OrderList_default as OrderList, OrderShipments_default as OrderShipments, OrderSummary_default as OrderSummary, OrderTotals_default as OrderTotals, PriceToggle_default as PriceToggle, ProductBulkPricesWithProvider_default as ProductBulkPrices, ProductBundles_default as ProductBundles, ProductCard_default as ProductCard, ProductDescription_default as ProductDescription, ProductDownloads_default as ProductDownloads, ProductGallery_default as ProductGallery, ProductGrid_default as ProductGrid, ProductGridConfigProvider, ProductInfo_default as ProductInfo, ProductPriceWithProvider_default as ProductPrice, ProductShortDescriptionWithProvider_default as ProductShortDescription, ProductSlider_default as ProductSlider, ProductSpecifications_default as ProductSpecifications, ProductTabs_default as ProductTabs, ProductVideos_default as ProductVideos, PropellerDepsProvider, PropellerProvider, PurchaseAuthorizationConfigurator_default as PurchaseAuthorizationConfigurator, PurchaseAuthorizationRequests_default as PurchaseAuthorizationRequests, QuoteActions_default as QuoteActions, RegisterForm_default as RegisterForm, SearchBar_default as SearchBar, UserDetails_default as UserDetails, buildRootMachinesQuery, fetchActiveCart, initCart, mergeAnonymousCart, useAddress, useAuth, useCart, useCheckout, useClusterConfigurator, useCompany, useFavorites, useInfraProps, useMachines, useMenu, useOrders, useProductBundles, useProductGridConfig, useProductInfo, useProductSearch, useProductSlider, useProductSpecs, usePropellerContext, usePropellerDeps, usePropellerScope, usePurchaseAuthorizationConfigurator, usePurchaseAuthorizationRequests, useRequiredPropellerDeps, useResolvedProps, useServices, useSpareParts, useUserMode };
18656
+ export { AccountIconAndMenu_default as AccountIconAndMenu, ActionCode_default as ActionCode, AddToCart_default as AddToCart, AddToFavorite_default as AddToFavorite, AddressCard_default as AddressCard, AddressSelector_default as AddressSelector, BreadcrumbsWithProvider_default as Breadcrumbs, CartBonusItems_default as CartBonusItems, CartCarriers_default as CartCarriers, CartIconAndSidebar_default as CartIconAndSidebar, CartItem_default as CartItem, CartOverview_default as CartOverview, CartPaymethods_default as CartPaymethods, CartSummary_default as CartSummary, CategoryDescription_default as CategoryDescription, CategoryShortDescriptionWithProvider_default as CategoryShortDescription, ClusterCard_default as ClusterCard, ClusterConfigurator_default as ClusterConfigurator, ClusterInfo_default as ClusterInfo, ClusterOptions_default as ClusterOptions, CompanySwitcher_default as CompanySwitcher, AddToCart_default as DefaultAddToCart, AddToFavorite_default as DefaultAddToFavorite, ItemStock_default as DefaultItemStock, DefaultProductBadges, ProductBulkPrices_default as DefaultProductBulkPrices, ProductBundles_default as DefaultProductBundles, DefaultProductImage, ProductPrice_default as DefaultProductPrice, DefaultProductSurcharges, DeliveryDate_default as DeliveryDate, FavoriteListDetails_default as FavoriteListDetails, FavoriteListItem_default as FavoriteListItem, FavoriteLists_default as FavoriteLists, ForgotPassword_default as ForgotPassword, GridFilters_default as GridFilters, GridFiltersPanel, GridPagination_default as GridPagination, GridTitleWithProvider_default as GridTitle, GridToolbar_default as GridToolbar, ItemStock_default as ItemStock, ItemsOverview_default as ItemsOverview, LoginForm_default as LoginForm, MachineCard, MachineGrid, Menu_default as Menu, OrderActions_default as OrderActions, OrderBonusItems_default as OrderBonusItems, OrderItemCard_default as OrderItemCard, OrderList_default as OrderList, OrderShipments_default as OrderShipments, OrderSummary_default as OrderSummary, OrderTotals_default as OrderTotals, PriceToggle_default as PriceToggle, ProductBulkPricesWithProvider_default as ProductBulkPrices, ProductBundles_default as ProductBundles, ProductCard_default as ProductCard, ProductDescription_default as ProductDescription, ProductDownloads_default as ProductDownloads, ProductGallery_default as ProductGallery, ProductGrid_default as ProductGrid, ProductGridConfigProvider, ProductInfo_default as ProductInfo, ProductPriceWithProvider_default as ProductPrice, ProductShortDescriptionWithProvider_default as ProductShortDescription, ProductSlider_default as ProductSlider, ProductSpecifications_default as ProductSpecifications, ProductTabs_default as ProductTabs, ProductVideos_default as ProductVideos, PropellerDepsProvider, PropellerProvider, PurchaseAuthorizationConfigurator_default as PurchaseAuthorizationConfigurator, PurchaseAuthorizationRequests_default as PurchaseAuthorizationRequests, QuickOrder, QuoteActions_default as QuoteActions, RegisterForm_default as RegisterForm, SearchBar_default as SearchBar, UserDetails_default as UserDetails, buildRootMachinesQuery, fetchActiveCart, initCart, mergeAnonymousCart, useAddress, useAuth, useCart, useCheckout, useClusterConfigurator, useCompany, useFavorites, useInfraProps, useMachines, useMenu, useOrders, useProductBundles, useProductGridConfig, useProductInfo, useProductSearch, useProductSlider, useProductSpecs, usePropellerContext, usePropellerDeps, usePropellerScope, usePurchaseAuthorizationConfigurator, usePurchaseAuthorizationRequests, useQuickOrder, useRequiredPropellerDeps, useResolvedProps, useServices, useSpareParts, useUserMode };
18182
18657
  //# sourceMappingURL=index.js.map
18183
18658
  //# sourceMappingURL=index.js.map