@ticketboothapp/booking 1.2.141 → 1.2.143
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/package.json
CHANGED
|
@@ -155,7 +155,9 @@ const checkoutPanelPropKeys = [
|
|
|
155
155
|
'receiptTotal',
|
|
156
156
|
'originalReceipt',
|
|
157
157
|
'priceSummaryLinesIncludeTaxRow',
|
|
158
|
-
'
|
|
158
|
+
'serverAmendmentLines',
|
|
159
|
+
'serverAmountDue',
|
|
160
|
+
'serverUpdatedTotal',
|
|
159
161
|
'isTaxIncludedInPrice',
|
|
160
162
|
'taxRate',
|
|
161
163
|
'currency',
|
|
@@ -971,7 +971,18 @@ export function AdminChangeBookingFlow({
|
|
|
971
971
|
receiptTax: adminFeAuthoritativeReceipt.tax,
|
|
972
972
|
receiptTotal: adminFeAuthoritativeReceipt.total,
|
|
973
973
|
originalReceipt,
|
|
974
|
-
|
|
974
|
+
serverAmendmentLines:
|
|
975
|
+
latestChangeQuote?.pricingVersion === 'booking-amendment-v1'
|
|
976
|
+
? latestChangeQuote.serverPreview?.priceSummaryLines ?? []
|
|
977
|
+
: null,
|
|
978
|
+
serverAmountDue:
|
|
979
|
+
latestChangeQuote?.pricingVersion === 'booking-amendment-v1'
|
|
980
|
+
? latestChangeQuote.serverPreview?.amountDue ?? null
|
|
981
|
+
: null,
|
|
982
|
+
serverUpdatedTotal:
|
|
983
|
+
latestChangeQuote?.pricingVersion === 'booking-amendment-v1'
|
|
984
|
+
? latestChangeQuote.serverPreview?.totalNewBooking ?? null
|
|
985
|
+
: null,
|
|
975
986
|
...{ priceSummaryLinesIncludeTaxRow, isTaxIncludedInPrice, showProviderPricingInlineEditor },
|
|
976
987
|
taxRate: pricingConfig?.taxRate,
|
|
977
988
|
...{ providerPricingUi, providerQuotedLines },
|
|
@@ -30,7 +30,9 @@ export interface AdminChangeCheckoutPanelProps {
|
|
|
30
30
|
receiptTotal: number;
|
|
31
31
|
originalReceipt: ChangeBookingFlowProps['originalReceipt'];
|
|
32
32
|
priceSummaryLinesIncludeTaxRow: boolean;
|
|
33
|
-
|
|
33
|
+
serverAmendmentLines: PriceSummaryLine[] | null;
|
|
34
|
+
serverAmountDue: number | null;
|
|
35
|
+
serverUpdatedTotal: number | null;
|
|
34
36
|
isTaxIncludedInPrice: boolean;
|
|
35
37
|
taxRate?: number;
|
|
36
38
|
currency: Currency;
|
|
@@ -100,7 +102,9 @@ export function AdminChangeCheckoutPanel({
|
|
|
100
102
|
receiptTotal,
|
|
101
103
|
originalReceipt,
|
|
102
104
|
priceSummaryLinesIncludeTaxRow,
|
|
103
|
-
|
|
105
|
+
serverAmendmentLines,
|
|
106
|
+
serverAmountDue,
|
|
107
|
+
serverUpdatedTotal,
|
|
104
108
|
isTaxIncludedInPrice,
|
|
105
109
|
taxRate,
|
|
106
110
|
currency,
|
|
@@ -183,26 +187,16 @@ export function AdminChangeCheckoutPanel({
|
|
|
183
187
|
isAdmin && originalReceipt ? (
|
|
184
188
|
<AdminChangeReceiptComparison
|
|
185
189
|
originalReceipt={originalReceipt}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}}
|
|
191
|
-
newPriceSummaryLines={priceSummaryLines}
|
|
192
|
-
newPriceSummaryLinesIncludeTaxRow={priceSummaryLinesIncludeTaxRow}
|
|
193
|
-
isServerAmendmentQuote={isServerAmendmentQuote}
|
|
194
|
-
amountDue={changeFlowAmountDue}
|
|
190
|
+
amendmentLines={serverAmendmentLines}
|
|
191
|
+
amountDue={serverAmountDue}
|
|
192
|
+
updatedTotal={serverUpdatedTotal}
|
|
193
|
+
selectionChanged={hasEffectiveChangeSelection}
|
|
195
194
|
amountDueLabel={totalSummaryLabel}
|
|
196
195
|
currency={currency}
|
|
197
196
|
locale={locale}
|
|
198
197
|
t={t}
|
|
199
198
|
taxRate={taxRate}
|
|
200
|
-
|
|
201
|
-
lineAmountInputs={lineAmountInputs}
|
|
202
|
-
onLineAmountInputChange={onLineAmountInputChange}
|
|
203
|
-
onLineAmountInputBlur={onLineAmountInputBlur}
|
|
204
|
-
onLineAmountReset={onLineAmountReset}
|
|
205
|
-
lineLabelInputs={lineLabelInputs}
|
|
199
|
+
adjustments={pricingAdjustments}
|
|
206
200
|
/>
|
|
207
201
|
) : null;
|
|
208
202
|
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { AdminFeAuthoritativeReceipt } from '../../lib/booking-api';
|
|
3
|
-
import { roundMoney } from '../../lib/booking/change-flow-pricing';
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
4
2
|
import { mapQuoteLineItemsToPriceSummaryLines } from '../../lib/booking/change-booking-server-preview';
|
|
5
3
|
import { formatCurrencyAmount } from '../../lib/currency';
|
|
6
4
|
import type { Locale } from '../../lib/booking/i18n/config';
|
|
@@ -13,240 +11,63 @@ type OriginalReceipt = NonNullable<ChangeBookingFlowProps['originalReceipt']>;
|
|
|
13
11
|
|
|
14
12
|
export interface AdminChangeReceiptComparisonProps {
|
|
15
13
|
originalReceipt: OriginalReceipt;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
/** Amendment-only rows returned by the Pricing V2 server quote. */
|
|
15
|
+
amendmentLines: PriceSummaryLine[] | null;
|
|
16
|
+
/** Signed server balance delta. Never calculated by this component. */
|
|
17
|
+
amountDue: number | null;
|
|
18
|
+
/** Full resulting booking total returned by the same server quote. */
|
|
19
|
+
updatedTotal: number | null;
|
|
20
|
+
selectionChanged: boolean;
|
|
21
21
|
amountDueLabel: string;
|
|
22
22
|
currency: Currency;
|
|
23
23
|
locale: string;
|
|
24
24
|
t: TranslationFn;
|
|
25
25
|
taxRate?: number;
|
|
26
|
-
|
|
27
|
-
lineAmountInputs?: Record<string, string>;
|
|
28
|
-
onLineAmountInputChange?: (lineKey: string, value: string) => void;
|
|
29
|
-
onLineAmountInputBlur?: (lineKey: string) => void;
|
|
30
|
-
onLineAmountReset?: (lineKey: string) => void;
|
|
31
|
-
lineLabelInputs?: Record<string, string>;
|
|
26
|
+
adjustments?: ReactNode;
|
|
32
27
|
}
|
|
33
28
|
|
|
34
29
|
function normalizeLocale(locale: string): Locale {
|
|
35
30
|
return locale === 'fr' ? 'fr' : 'en';
|
|
36
31
|
}
|
|
37
32
|
|
|
33
|
+
function originalLines(receipt: OriginalReceipt): PriceSummaryLine[] {
|
|
34
|
+
return receipt.lineItems?.length
|
|
35
|
+
? mapQuoteLineItemsToPriceSummaryLines(receipt.lineItems)
|
|
36
|
+
: [];
|
|
37
|
+
}
|
|
38
|
+
|
|
38
39
|
function hasTaxLine(lines: PriceSummaryLine[]): boolean {
|
|
39
40
|
return lines.some(
|
|
40
41
|
(line) => line.kind === 'line' && String(line.type ?? '').toUpperCase() === 'TAX',
|
|
41
42
|
);
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
function mapOriginalReceiptLines(originalReceipt: OriginalReceipt): PriceSummaryLine[] {
|
|
45
|
-
if (!originalReceipt.lineItems?.length) return [];
|
|
46
|
-
return mapQuoteLineItemsToPriceSummaryLines(originalReceipt.lineItems);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function lineIdentity(line: PriceSummaryLine): string {
|
|
50
|
-
const normalizedLabel = (value: string) =>
|
|
51
|
-
value
|
|
52
|
-
.replace(/\s*\([^)]*(?:people|person|x\s*\d+)[^)]*\)\s*$/i, '')
|
|
53
|
-
.replace(/\s+/g, ' ')
|
|
54
|
-
.trim()
|
|
55
|
-
.toUpperCase();
|
|
56
|
-
return line.kind === 'ticket'
|
|
57
|
-
? `ticket:${line.category.trim().toUpperCase()}`
|
|
58
|
-
: `line:${String(line.type ?? '').trim().toUpperCase()}:${normalizedLabel(line.label)}`;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function lineAmount(line: PriceSummaryLine): number {
|
|
62
|
-
return line.kind === 'ticket' ? line.itemTotal : line.amount;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function lineQuantity(line: PriceSummaryLine): number | null {
|
|
66
|
-
return line.kind === 'ticket' ? line.qty : line.quantity ?? null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function amendmentDeltaLines(
|
|
70
|
-
originalLines: PriceSummaryLine[],
|
|
71
|
-
proposedLines: PriceSummaryLine[],
|
|
72
|
-
): PriceSummaryLine[] {
|
|
73
|
-
const remainingOriginal = new Map<string, { amount: number; quantity: number | null }>();
|
|
74
|
-
for (const line of originalLines) {
|
|
75
|
-
const key = lineIdentity(line);
|
|
76
|
-
const previous = remainingOriginal.get(key);
|
|
77
|
-
remainingOriginal.set(key, {
|
|
78
|
-
amount: roundMoney((previous?.amount ?? 0) + lineAmount(line)),
|
|
79
|
-
quantity:
|
|
80
|
-
previous?.quantity == null && lineQuantity(line) == null
|
|
81
|
-
? null
|
|
82
|
-
: (previous?.quantity ?? 0) + (lineQuantity(line) ?? 0),
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return proposedLines.flatMap((line): PriceSummaryLine[] => {
|
|
87
|
-
const original = remainingOriginal.get(lineIdentity(line));
|
|
88
|
-
const proposedQuantity = lineQuantity(line);
|
|
89
|
-
const quantity =
|
|
90
|
-
proposedQuantity == null && original?.quantity == null
|
|
91
|
-
? null
|
|
92
|
-
: (proposedQuantity ?? 0) - (original?.quantity ?? 0);
|
|
93
|
-
const proposedAmount = lineAmount(line);
|
|
94
|
-
const amount = roundMoney(
|
|
95
|
-
quantity != null && quantity > 0 && proposedQuantity != null && proposedQuantity > 0
|
|
96
|
-
? (proposedAmount / proposedQuantity) * quantity
|
|
97
|
-
: proposedAmount - (original?.amount ?? 0),
|
|
98
|
-
);
|
|
99
|
-
if (Math.abs(amount) < 0.005 && (quantity == null || quantity === 0)) return [];
|
|
100
|
-
|
|
101
|
-
if (line.kind === 'ticket') {
|
|
102
|
-
return [{
|
|
103
|
-
...line,
|
|
104
|
-
qty: Math.abs(quantity ?? 0) || line.qty,
|
|
105
|
-
itemTotal: amount,
|
|
106
|
-
editable: false,
|
|
107
|
-
lineKey: undefined,
|
|
108
|
-
}];
|
|
109
|
-
}
|
|
110
|
-
const isTax = String(line.type ?? '').toUpperCase() === 'TAX';
|
|
111
|
-
return [{
|
|
112
|
-
...line,
|
|
113
|
-
label: isTax
|
|
114
|
-
? (amount >= 0 ? 'Additional tax' : 'Tax reduction')
|
|
115
|
-
: line.label.replace(/\s*\([^)]*(?:people|person|x\s*\d+)[^)]*\)\s*$/i, '').trim(),
|
|
116
|
-
amount,
|
|
117
|
-
quantity: quantity == null ? null : Math.abs(quantity),
|
|
118
|
-
editable: false,
|
|
119
|
-
lineKey: undefined,
|
|
120
|
-
}];
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
45
|
export function AdminChangeReceiptComparison({
|
|
125
46
|
originalReceipt,
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
47
|
+
amendmentLines,
|
|
48
|
+
amountDue,
|
|
49
|
+
updatedTotal,
|
|
50
|
+
selectionChanged,
|
|
129
51
|
amountDueLabel,
|
|
130
52
|
currency,
|
|
131
53
|
locale,
|
|
132
54
|
t,
|
|
133
55
|
taxRate,
|
|
134
|
-
|
|
56
|
+
adjustments,
|
|
135
57
|
}: AdminChangeReceiptComparisonProps) {
|
|
136
58
|
const displayLocale = normalizeLocale(locale);
|
|
137
|
-
const
|
|
138
|
-
() => mapOriginalReceiptLines(originalReceipt),
|
|
139
|
-
[originalReceipt],
|
|
140
|
-
);
|
|
141
|
-
const originalLinesIncludeTaxRow = hasTaxLine(originalReceiptLines);
|
|
142
|
-
const calculatedChangeLines = useMemo(() => {
|
|
143
|
-
if (!isServerAmendmentQuote) {
|
|
144
|
-
return amendmentDeltaLines(originalReceiptLines, newPriceSummaryLines);
|
|
145
|
-
}
|
|
146
|
-
const nonTax = newPriceSummaryLines.filter(
|
|
147
|
-
(line) => !(line.kind === 'line' && String(line.type ?? '').toUpperCase() === 'TAX'),
|
|
148
|
-
);
|
|
149
|
-
const tax = roundMoney(
|
|
150
|
-
newPriceSummaryLines.reduce(
|
|
151
|
-
(sum, line) =>
|
|
152
|
-
line.kind === 'line' && String(line.type ?? '').toUpperCase() === 'TAX'
|
|
153
|
-
? sum + line.amount
|
|
154
|
-
: sum,
|
|
155
|
-
0,
|
|
156
|
-
),
|
|
157
|
-
);
|
|
158
|
-
return Math.abs(tax) < 0.005
|
|
159
|
-
? nonTax
|
|
160
|
-
: [
|
|
161
|
-
...nonTax,
|
|
162
|
-
{
|
|
163
|
-
kind: 'line' as const,
|
|
164
|
-
label: tax >= 0 ? 'Additional tax' : 'Tax reduction',
|
|
165
|
-
amount: tax,
|
|
166
|
-
type: 'TAX',
|
|
167
|
-
},
|
|
168
|
-
];
|
|
169
|
-
}, [isServerAmendmentQuote, newPriceSummaryLines, originalReceiptLines]);
|
|
170
|
-
const nonTaxChangeSubtotal = roundMoney(
|
|
171
|
-
calculatedChangeLines.reduce(
|
|
172
|
-
(sum, line) =>
|
|
173
|
-
line.kind === 'line' && String(line.type ?? '').toUpperCase() === 'TAX'
|
|
174
|
-
? sum
|
|
175
|
-
: sum + lineAmount(line),
|
|
176
|
-
0,
|
|
177
|
-
),
|
|
178
|
-
);
|
|
179
|
-
const receiptTaxDelta = roundMoney(
|
|
180
|
-
isServerAmendmentQuote
|
|
181
|
-
? calculatedChangeLines.reduce(
|
|
182
|
-
(sum, line) =>
|
|
183
|
-
line.kind === 'line' && String(line.type ?? '').toUpperCase() === 'TAX'
|
|
184
|
-
? sum + line.amount
|
|
185
|
-
: sum,
|
|
186
|
-
0,
|
|
187
|
-
)
|
|
188
|
-
: taxRate != null && nonTaxChangeSubtotal > 0
|
|
189
|
-
? nonTaxChangeSubtotal * taxRate
|
|
190
|
-
: newReceipt.tax - originalReceipt.tax,
|
|
191
|
-
);
|
|
192
|
-
const changeLines = useMemo(() => {
|
|
193
|
-
if (hasTaxLine(calculatedChangeLines) || Math.abs(receiptTaxDelta) < 0.005) {
|
|
194
|
-
return calculatedChangeLines;
|
|
195
|
-
}
|
|
196
|
-
return [
|
|
197
|
-
...calculatedChangeLines,
|
|
198
|
-
{
|
|
199
|
-
kind: 'line' as const,
|
|
200
|
-
label: receiptTaxDelta >= 0 ? 'Additional tax' : 'Tax reduction',
|
|
201
|
-
amount: receiptTaxDelta,
|
|
202
|
-
type: 'TAX',
|
|
203
|
-
},
|
|
204
|
-
];
|
|
205
|
-
}, [calculatedChangeLines, receiptTaxDelta]);
|
|
206
|
-
const changeLinesIncludeTaxRow = hasTaxLine(changeLines);
|
|
207
|
-
const changeTax = roundMoney(
|
|
208
|
-
changeLines.reduce(
|
|
209
|
-
(sum, line) =>
|
|
210
|
-
line.kind === 'line' && String(line.type ?? '').toUpperCase() === 'TAX'
|
|
211
|
-
? sum + line.amount
|
|
212
|
-
: sum,
|
|
213
|
-
0,
|
|
214
|
-
),
|
|
215
|
-
);
|
|
216
|
-
const displayedAmountDue = roundMoney(changeLines.reduce((sum, line) => sum + lineAmount(line), 0));
|
|
217
|
-
const displayedUpdatedTotal = roundMoney(originalReceipt.total + displayedAmountDue);
|
|
218
|
-
const displayedUpdatedSubtotal = roundMoney(originalReceipt.subtotal + nonTaxChangeSubtotal);
|
|
219
|
-
const displayedUpdatedTax = roundMoney(originalReceipt.tax + changeTax);
|
|
59
|
+
const existingLines = originalLines(originalReceipt);
|
|
220
60
|
const originalCurrency = originalReceipt.currency ?? currency;
|
|
221
|
-
const
|
|
222
|
-
() =>
|
|
223
|
-
originalReceiptLines.map((line) =>
|
|
224
|
-
line.kind === 'ticket' && line.qty > 0
|
|
225
|
-
? {
|
|
226
|
-
...line,
|
|
227
|
-
breakdown: line.breakdown ?? {
|
|
228
|
-
lineItems: [],
|
|
229
|
-
subtotalAfterAdjustments: roundMoney(line.itemTotal / line.qty),
|
|
230
|
-
feeLines: [],
|
|
231
|
-
taxRate: 0,
|
|
232
|
-
taxAmount: 0,
|
|
233
|
-
isTaxIncluded: false,
|
|
234
|
-
finalPrice: roundMoney(line.itemTotal / line.qty),
|
|
235
|
-
},
|
|
236
|
-
}
|
|
237
|
-
: line,
|
|
238
|
-
),
|
|
239
|
-
[originalReceiptLines],
|
|
240
|
-
);
|
|
61
|
+
const quoteReady = amendmentLines != null && amountDue != null && updatedTotal != null;
|
|
241
62
|
const amountDueClass =
|
|
242
|
-
|
|
63
|
+
amountDue != null && amountDue < -0.005
|
|
243
64
|
? 'text-emerald-700'
|
|
244
|
-
:
|
|
65
|
+
: amountDue != null && amountDue > 0.005
|
|
245
66
|
? 'text-stone-900'
|
|
246
67
|
: 'text-stone-700';
|
|
247
68
|
|
|
248
69
|
return (
|
|
249
|
-
<div className="space-y-3
|
|
70
|
+
<div className="min-w-0 space-y-3 overflow-visible">
|
|
250
71
|
<div className="grid gap-4 md:grid-cols-2">
|
|
251
72
|
<section className="min-w-0 overflow-visible rounded-lg border border-stone-200 bg-stone-50/70 p-3">
|
|
252
73
|
<div className="mb-3 flex items-center justify-between gap-3">
|
|
@@ -256,89 +77,77 @@ export function AdminChangeReceiptComparison({
|
|
|
256
77
|
</span>
|
|
257
78
|
</div>
|
|
258
79
|
<PriceSummary
|
|
259
|
-
lines={
|
|
80
|
+
lines={existingLines}
|
|
260
81
|
total={originalReceipt.total}
|
|
261
82
|
currency={originalCurrency}
|
|
262
83
|
locale={displayLocale}
|
|
263
84
|
subtotal={originalReceipt.subtotal}
|
|
264
|
-
taxAmount={
|
|
85
|
+
taxAmount={hasTaxLine(existingLines) ? 0 : originalReceipt.tax}
|
|
265
86
|
taxRate={taxRate}
|
|
266
87
|
size="sm"
|
|
267
88
|
t={t}
|
|
268
89
|
/>
|
|
269
90
|
</section>
|
|
91
|
+
|
|
270
92
|
<section className="min-w-0 overflow-visible rounded-lg border border-sky-200 bg-sky-50/50 p-3">
|
|
271
93
|
<div className="mb-3 flex items-center justify-between gap-3">
|
|
272
94
|
<div>
|
|
273
95
|
<h3 className="text-sm font-semibold text-stone-900">This change</h3>
|
|
274
|
-
<p className="mt-0.5 text-xs text-stone-500">
|
|
96
|
+
<p className="mt-0.5 text-xs text-stone-500">Server-priced amendment</p>
|
|
275
97
|
</div>
|
|
276
98
|
<span className="shrink-0 text-xs font-medium text-stone-500">
|
|
277
|
-
{formatCurrencyAmount(
|
|
99
|
+
{amountDue == null ? '—' : formatCurrencyAmount(amountDue, currency, displayLocale)}
|
|
278
100
|
</span>
|
|
279
101
|
</div>
|
|
280
|
-
{
|
|
102
|
+
{!selectionChanged ? (
|
|
103
|
+
<div className="rounded-md border border-dashed border-stone-300 bg-white/70 px-3 py-4 text-center">
|
|
104
|
+
<p className="text-sm font-medium text-stone-700">No changes selected</p>
|
|
105
|
+
<p className="mt-1 text-xs text-stone-500">Change tickets or another booking option to request a price.</p>
|
|
106
|
+
</div>
|
|
107
|
+
) : !quoteReady ? (
|
|
108
|
+
<div className="rounded-md border border-dashed border-stone-300 bg-white/70 px-3 py-4 text-center">
|
|
109
|
+
<p className="text-sm font-medium text-stone-700">Waiting for server price…</p>
|
|
110
|
+
</div>
|
|
111
|
+
) : amendmentLines.length === 0 && Math.abs(amountDue) < 0.005 ? (
|
|
281
112
|
<div className="rounded-md border border-dashed border-stone-300 bg-white/70 px-3 py-4 text-center">
|
|
282
113
|
<p className="text-sm font-medium text-stone-700">No pricing changes</p>
|
|
283
|
-
<p className="mt-1 text-xs text-stone-500">The selected booking details match the existing purchase.</p>
|
|
284
114
|
</div>
|
|
285
115
|
) : (
|
|
286
116
|
<PriceSummary
|
|
287
|
-
lines={
|
|
288
|
-
total={
|
|
117
|
+
lines={amendmentLines}
|
|
118
|
+
total={amountDue}
|
|
289
119
|
currency={currency}
|
|
290
120
|
locale={displayLocale}
|
|
291
|
-
subtotal={nonTaxChangeSubtotal}
|
|
292
|
-
taxAmount={!changeLinesIncludeTaxRow ? changeTax : 0}
|
|
293
|
-
taxRate={taxRate}
|
|
294
121
|
size="sm"
|
|
295
122
|
t={t}
|
|
296
123
|
/>
|
|
297
124
|
)}
|
|
298
125
|
</section>
|
|
299
126
|
</div>
|
|
300
|
-
|
|
301
|
-
|
|
127
|
+
|
|
128
|
+
{quoteReady ? (
|
|
129
|
+
<section className="flex flex-wrap items-center justify-between gap-2 rounded-lg border border-stone-300 bg-white px-3 py-3 shadow-sm">
|
|
302
130
|
<div>
|
|
303
|
-
<h3 className="text-sm font-semibold text-stone-900">Updated booking
|
|
304
|
-
<p className="mt-0.5 text-xs text-stone-500">
|
|
131
|
+
<h3 className="text-sm font-semibold text-stone-900">Updated booking total</h3>
|
|
132
|
+
<p className="mt-0.5 text-xs text-stone-500">Authoritative total returned by the server quote</p>
|
|
305
133
|
</div>
|
|
306
|
-
<span className="
|
|
307
|
-
{formatCurrencyAmount(
|
|
134
|
+
<span className="text-lg font-semibold tabular-nums text-stone-900">
|
|
135
|
+
{formatCurrencyAmount(updatedTotal, currency, displayLocale)}
|
|
308
136
|
</span>
|
|
309
|
-
</
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
(line) => !(line.kind === 'line' && String(line.type ?? '').toUpperCase() === 'TAX'),
|
|
314
|
-
),
|
|
315
|
-
...changeLines.filter(
|
|
316
|
-
(line) => !(line.kind === 'line' && String(line.type ?? '').toUpperCase() === 'TAX'),
|
|
317
|
-
),
|
|
318
|
-
].map((line) => ({ ...line, editable: false, lineKey: undefined }))}
|
|
319
|
-
total={displayedUpdatedTotal}
|
|
320
|
-
currency={currency}
|
|
321
|
-
locale={displayLocale}
|
|
322
|
-
subtotal={displayedUpdatedSubtotal}
|
|
323
|
-
taxAmount={displayedUpdatedTax}
|
|
324
|
-
taxRate={taxRate}
|
|
325
|
-
size="sm"
|
|
326
|
-
t={t}
|
|
327
|
-
/>
|
|
328
|
-
</section>
|
|
329
|
-
{newReceiptAdjustments ? (
|
|
137
|
+
</section>
|
|
138
|
+
) : null}
|
|
139
|
+
|
|
140
|
+
{adjustments ? (
|
|
330
141
|
<section className="rounded-lg border border-amber-200 bg-amber-50/40 p-3">
|
|
331
|
-
<
|
|
332
|
-
|
|
333
|
-
<p className="mt-0.5 text-xs text-stone-500">Optional structured charges or credits applied to this amendment</p>
|
|
334
|
-
</div>
|
|
335
|
-
{newReceiptAdjustments}
|
|
142
|
+
<h3 className="mb-2 text-sm font-semibold text-stone-900">Admin adjustments</h3>
|
|
143
|
+
{adjustments}
|
|
336
144
|
</section>
|
|
337
145
|
) : null}
|
|
146
|
+
|
|
338
147
|
<div className="flex flex-wrap items-center justify-between gap-2 rounded-lg border border-stone-200 bg-stone-50 px-3 py-2">
|
|
339
148
|
<span className="text-sm font-semibold text-stone-900">{amountDueLabel}</span>
|
|
340
149
|
<span className={`text-lg font-semibold tabular-nums ${amountDueClass}`}>
|
|
341
|
-
{formatCurrencyAmount(
|
|
150
|
+
{!selectionChanged || amountDue == null ? '—' : formatCurrencyAmount(amountDue, currency, displayLocale)}
|
|
342
151
|
</span>
|
|
343
152
|
</div>
|
|
344
153
|
</div>
|