@metron-labs/quote-react 0.1.42 → 0.1.44
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/app/QuickQuote.js +37 -5
- package/dist/app/stages/BasketStage.d.ts +3 -1
- package/dist/app/stages/BasketStage.js +13 -7
- package/dist/app/stages/EntryStage.js +3 -0
- package/dist/app/stages/ScheduleResultsStage.js +5 -0
- package/dist/embed/quote.js +38 -33
- package/dist/lib/boq/inputLimits.d.ts +2 -0
- package/dist/lib/boq/inputLimits.js +2 -0
- package/dist/lib/config.js +2 -1
- package/dist/lib/useEscapeKey.d.ts +2 -0
- package/dist/lib/useEscapeKey.js +20 -0
- package/dist/styles.css +25 -20
- package/dist/util/pricing.js +3 -2
- package/package.json +1 -1
|
@@ -3,6 +3,8 @@ export declare const MAX_LINES = 500;
|
|
|
3
3
|
export declare const MAX_CHARS = 50000;
|
|
4
4
|
/** Longer than this and it's usually a pasted paragraph, not a line item. */
|
|
5
5
|
export declare const MAX_LINE_CHARS = 500;
|
|
6
|
+
/** Most of one product a basket line can ask for. Under the quote-request API's own 1,000,000 cap. */
|
|
7
|
+
export declare const MAX_ORDER_QTY = 100000;
|
|
6
8
|
export declare const FILE_EXTENSIONS: {
|
|
7
9
|
schedule: string[];
|
|
8
10
|
photo: string[];
|
|
@@ -9,6 +9,8 @@ export const MAX_LINES = 500;
|
|
|
9
9
|
export const MAX_CHARS = 50000;
|
|
10
10
|
/** Longer than this and it's usually a pasted paragraph, not a line item. */
|
|
11
11
|
export const MAX_LINE_CHARS = 500;
|
|
12
|
+
/** Most of one product a basket line can ask for. Under the quote-request API's own 1,000,000 cap. */
|
|
13
|
+
export const MAX_ORDER_QTY = 100000;
|
|
12
14
|
/** Photos are scaled down to this before upload - plenty for reading a list. */
|
|
13
15
|
const PHOTO_MAX_EDGE_PX = 2000;
|
|
14
16
|
export const FILE_EXTENSIONS = {
|
package/dist/lib/config.js
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
let moduleConfig = {};
|
|
9
9
|
/** Called by QuoteProvider. Not part of the public API. */
|
|
10
10
|
export function setModuleConfig(config) {
|
|
11
|
-
|
|
11
|
+
// Codes are issued upper-case; a host that types or lower-cases one still gets its client.
|
|
12
|
+
moduleConfig = { ...config, client: config.client?.trim().toUpperCase() || undefined };
|
|
12
13
|
}
|
|
13
14
|
/** The active config, for code outside the React tree. */
|
|
14
15
|
export function getQuoteConfig() {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
/** Calls `onEscape` when Escape is pressed while `active` - for closing a dialog. */
|
|
3
|
+
export function useEscapeKey(active, onEscape) {
|
|
4
|
+
// Latest handler without re-subscribing on every render.
|
|
5
|
+
const handler = useRef(onEscape);
|
|
6
|
+
handler.current = onEscape;
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (!active)
|
|
9
|
+
return;
|
|
10
|
+
const onKeyDown = (event) => {
|
|
11
|
+
// isComposing: Escape cancelling an IME composition, not meant for the dialog.
|
|
12
|
+
if (event.key !== "Escape" || event.defaultPrevented || event.isComposing)
|
|
13
|
+
return;
|
|
14
|
+
event.preventDefault();
|
|
15
|
+
handler.current();
|
|
16
|
+
};
|
|
17
|
+
document.addEventListener("keydown", onKeyDown);
|
|
18
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
19
|
+
}, [active]);
|
|
20
|
+
}
|
package/dist/styles.css
CHANGED
|
@@ -948,8 +948,12 @@
|
|
|
948
948
|
|
|
949
949
|
:where(.app-wrapper) .basket-card-stats {
|
|
950
950
|
display: grid;
|
|
951
|
-
|
|
952
|
-
|
|
951
|
+
/* Equal qty columns so both wrap their unit (or neither) and stay aligned. */
|
|
952
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 128px;
|
|
953
|
+
/* Top-aligned so a wrapped unit under the qty box doesn't shift the row;
|
|
954
|
+
the total centres on the box via the shared height below. */
|
|
955
|
+
--basket-qty-h: 34px;
|
|
956
|
+
align-items: start;
|
|
953
957
|
gap: 16px 28px;
|
|
954
958
|
margin-top: 16px;
|
|
955
959
|
padding-top: 5px;
|
|
@@ -979,10 +983,14 @@
|
|
|
979
983
|
color: var(--text-dim);
|
|
980
984
|
}
|
|
981
985
|
|
|
986
|
+
/* Wraps so the unit drops under the qty box when the host page makes the
|
|
987
|
+
card narrower than the viewport breakpoints expect, instead of running
|
|
988
|
+
into the next column. */
|
|
982
989
|
:where(.app-wrapper) .basket-stat-value {
|
|
983
990
|
display: flex;
|
|
991
|
+
flex-wrap: wrap;
|
|
984
992
|
align-items: baseline;
|
|
985
|
-
gap: 6px;
|
|
993
|
+
gap: 2px 6px;
|
|
986
994
|
}
|
|
987
995
|
|
|
988
996
|
:where(.app-wrapper) .basket-stat--right .basket-stat-value {
|
|
@@ -991,6 +999,12 @@
|
|
|
991
999
|
|
|
992
1000
|
:where(.app-wrapper) .basket-stat--total .basket-stat-value {
|
|
993
1001
|
justify-content: flex-end;
|
|
1002
|
+
min-height: var(--basket-qty-h);
|
|
1003
|
+
align-content: center;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
:where(.app-wrapper) .basket-qty-field .bom-qty-edit {
|
|
1007
|
+
height: var(--basket-qty-h);
|
|
994
1008
|
}
|
|
995
1009
|
|
|
996
1010
|
:where(.app-wrapper) .basket-stat-value--muted {
|
|
@@ -1111,11 +1125,18 @@
|
|
|
1111
1125
|
:where(.app-wrapper) .basket-sidebar-row {
|
|
1112
1126
|
display: flex;
|
|
1113
1127
|
justify-content: space-between;
|
|
1128
|
+
align-items: baseline;
|
|
1129
|
+
/* A long label in the narrow sidebar wraps rather than running into the amount. */
|
|
1130
|
+
gap: 12px;
|
|
1114
1131
|
padding: 7px 0;
|
|
1115
1132
|
font-size: 14px;
|
|
1116
1133
|
color: var(--text-mid);
|
|
1117
1134
|
}
|
|
1118
1135
|
|
|
1136
|
+
:where(.app-wrapper) .basket-sidebar-row > :last-child {
|
|
1137
|
+
white-space: nowrap;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1119
1140
|
:where(.app-wrapper) .basket-sidebar-row--total {
|
|
1120
1141
|
border-top: 1px solid var(--border);
|
|
1121
1142
|
margin-top: 6px;
|
|
@@ -6612,11 +6633,6 @@
|
|
|
6612
6633
|
:where(.app-wrapper) .basket-stat--total {
|
|
6613
6634
|
grid-column: 1 / -1;
|
|
6614
6635
|
min-width: 0;
|
|
6615
|
-
text-align: left;
|
|
6616
|
-
}
|
|
6617
|
-
|
|
6618
|
-
:where(.app-wrapper) .basket-stat--total .basket-stat-value {
|
|
6619
|
-
justify-content: flex-start;
|
|
6620
6636
|
}
|
|
6621
6637
|
|
|
6622
6638
|
/* Basket cards on narrow phones: the 90px qty box + unit overflowed its
|
|
@@ -6628,6 +6644,7 @@
|
|
|
6628
6644
|
|
|
6629
6645
|
:where(.app-wrapper) .basket-card-stats {
|
|
6630
6646
|
gap: 14px 16px;
|
|
6647
|
+
--basket-qty-h: 44px;
|
|
6631
6648
|
}
|
|
6632
6649
|
|
|
6633
6650
|
:where(.app-wrapper) .basket-stat-label {
|
|
@@ -6649,7 +6666,6 @@
|
|
|
6649
6666
|
|
|
6650
6667
|
:where(.app-wrapper) .basket-qty-field .bom-qty-edit {
|
|
6651
6668
|
width: 100%;
|
|
6652
|
-
height: 44px;
|
|
6653
6669
|
padding-left: 8px;
|
|
6654
6670
|
padding-right: 36px;
|
|
6655
6671
|
}
|
|
@@ -6741,17 +6757,6 @@
|
|
|
6741
6757
|
/* Category links: one swipeable row instead of wrapping. */
|
|
6742
6758
|
}
|
|
6743
6759
|
|
|
6744
|
-
/* Small phones: basket item total sits on the right, label still above. */
|
|
6745
|
-
@media (max-width: 400px) {
|
|
6746
|
-
:where(.app-wrapper) .basket-stat--total {
|
|
6747
|
-
text-align: right;
|
|
6748
|
-
}
|
|
6749
|
-
|
|
6750
|
-
:where(.app-wrapper) .basket-stat--total .basket-stat-value {
|
|
6751
|
-
justify-content: flex-end;
|
|
6752
|
-
}
|
|
6753
|
-
}
|
|
6754
|
-
|
|
6755
6760
|
/* Very small phones: logo text pushed Account/Basket onto their own row.
|
|
6756
6761
|
Keep just the logo mark (the svg has a fixed px size, so font-size: 0
|
|
6757
6762
|
only hides the bare text node). Footer logo is untouched. */
|
package/dist/util/pricing.js
CHANGED
|
@@ -123,7 +123,8 @@ export function getPricingBreakdown(item, options) {
|
|
|
123
123
|
const salesUnitSize = item.saleUnitQuantity ?? item.salesUnitSize ?? item.uoiCount ?? 1;
|
|
124
124
|
const wasteFactor = options?.wasteFactorEnabled === false ? 0 : (options?.wasteFactorOverride ?? item.wasteFactorOverride ?? item.wasteFactorDefault ?? 0);
|
|
125
125
|
const hasOverride = item.sellUnitsOverride != null && Number.isFinite(item.sellUnitsOverride) && item.sellUnitsOverride > 0;
|
|
126
|
-
|
|
126
|
+
// A typed quantity follows the same rule as a calculated one: no part sheets or bags.
|
|
127
|
+
const overriddenSellUnits = hasOverride ? (discreteSellUnits.has(sellUnit) ? Math.ceil(item.sellUnitsOverride) : item.sellUnitsOverride) : null;
|
|
127
128
|
const review = (reason) => ({
|
|
128
129
|
requestedAmount,
|
|
129
130
|
requestedUnit,
|
|
@@ -267,7 +268,7 @@ export function getPricingBreakdown(item, options) {
|
|
|
267
268
|
return review(`Quantity review required, initial request needed ${requestedAmount} ${requestedUnit}.`);
|
|
268
269
|
}
|
|
269
270
|
const calculatedSellUnits = roundQuantity(rawSellUnits, roundingRule);
|
|
270
|
-
const requiredSellUnits = hasOverride ?
|
|
271
|
+
const requiredSellUnits = hasOverride ? overriddenSellUnits : calculatedSellUnits;
|
|
271
272
|
return {
|
|
272
273
|
requestedAmount,
|
|
273
274
|
requestedUnit,
|