@ticketboothapp/booking 1.2.141 → 1.2.142
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,15 @@ export function AdminChangeCheckoutPanel({
|
|
|
183
187
|
isAdmin && originalReceipt ? (
|
|
184
188
|
<AdminChangeReceiptComparison
|
|
185
189
|
originalReceipt={originalReceipt}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
total: receiptTotal,
|
|
190
|
-
}}
|
|
191
|
-
newPriceSummaryLines={priceSummaryLines}
|
|
192
|
-
newPriceSummaryLinesIncludeTaxRow={priceSummaryLinesIncludeTaxRow}
|
|
193
|
-
isServerAmendmentQuote={isServerAmendmentQuote}
|
|
194
|
-
amountDue={changeFlowAmountDue}
|
|
190
|
+
amendmentLines={serverAmendmentLines}
|
|
191
|
+
amountDue={serverAmountDue}
|
|
192
|
+
updatedTotal={serverUpdatedTotal}
|
|
195
193
|
amountDueLabel={totalSummaryLabel}
|
|
196
194
|
currency={currency}
|
|
197
195
|
locale={locale}
|
|
198
196
|
t={t}
|
|
199
197
|
taxRate={taxRate}
|
|
200
|
-
|
|
201
|
-
lineAmountInputs={lineAmountInputs}
|
|
202
|
-
onLineAmountInputChange={onLineAmountInputChange}
|
|
203
|
-
onLineAmountInputBlur={onLineAmountInputBlur}
|
|
204
|
-
onLineAmountReset={onLineAmountReset}
|
|
205
|
-
lineLabelInputs={lineLabelInputs}
|
|
198
|
+
adjustments={pricingAdjustments}
|
|
206
199
|
/>
|
|
207
200
|
) : null;
|
|
208
201
|
|
|
@@ -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,61 @@ 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;
|
|
21
20
|
amountDueLabel: string;
|
|
22
21
|
currency: Currency;
|
|
23
22
|
locale: string;
|
|
24
23
|
t: TranslationFn;
|
|
25
24
|
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>;
|
|
25
|
+
adjustments?: ReactNode;
|
|
32
26
|
}
|
|
33
27
|
|
|
34
28
|
function normalizeLocale(locale: string): Locale {
|
|
35
29
|
return locale === 'fr' ? 'fr' : 'en';
|
|
36
30
|
}
|
|
37
31
|
|
|
32
|
+
function originalLines(receipt: OriginalReceipt): PriceSummaryLine[] {
|
|
33
|
+
return receipt.lineItems?.length
|
|
34
|
+
? mapQuoteLineItemsToPriceSummaryLines(receipt.lineItems)
|
|
35
|
+
: [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
38
|
function hasTaxLine(lines: PriceSummaryLine[]): boolean {
|
|
39
39
|
return lines.some(
|
|
40
40
|
(line) => line.kind === 'line' && String(line.type ?? '').toUpperCase() === 'TAX',
|
|
41
41
|
);
|
|
42
42
|
}
|
|
43
43
|
|
|
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
44
|
export function AdminChangeReceiptComparison({
|
|
125
45
|
originalReceipt,
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
46
|
+
amendmentLines,
|
|
47
|
+
amountDue,
|
|
48
|
+
updatedTotal,
|
|
129
49
|
amountDueLabel,
|
|
130
50
|
currency,
|
|
131
51
|
locale,
|
|
132
52
|
t,
|
|
133
53
|
taxRate,
|
|
134
|
-
|
|
54
|
+
adjustments,
|
|
135
55
|
}: AdminChangeReceiptComparisonProps) {
|
|
136
56
|
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);
|
|
57
|
+
const existingLines = originalLines(originalReceipt);
|
|
220
58
|
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
|
-
);
|
|
59
|
+
const quoteReady = amendmentLines != null && amountDue != null && updatedTotal != null;
|
|
241
60
|
const amountDueClass =
|
|
242
|
-
|
|
61
|
+
amountDue != null && amountDue < -0.005
|
|
243
62
|
? 'text-emerald-700'
|
|
244
|
-
:
|
|
63
|
+
: amountDue != null && amountDue > 0.005
|
|
245
64
|
? 'text-stone-900'
|
|
246
65
|
: 'text-stone-700';
|
|
247
66
|
|
|
248
67
|
return (
|
|
249
|
-
<div className="space-y-3
|
|
68
|
+
<div className="min-w-0 space-y-3 overflow-visible">
|
|
250
69
|
<div className="grid gap-4 md:grid-cols-2">
|
|
251
70
|
<section className="min-w-0 overflow-visible rounded-lg border border-stone-200 bg-stone-50/70 p-3">
|
|
252
71
|
<div className="mb-3 flex items-center justify-between gap-3">
|
|
@@ -256,89 +75,72 @@ export function AdminChangeReceiptComparison({
|
|
|
256
75
|
</span>
|
|
257
76
|
</div>
|
|
258
77
|
<PriceSummary
|
|
259
|
-
lines={
|
|
78
|
+
lines={existingLines}
|
|
260
79
|
total={originalReceipt.total}
|
|
261
80
|
currency={originalCurrency}
|
|
262
81
|
locale={displayLocale}
|
|
263
82
|
subtotal={originalReceipt.subtotal}
|
|
264
|
-
taxAmount={
|
|
83
|
+
taxAmount={hasTaxLine(existingLines) ? 0 : originalReceipt.tax}
|
|
265
84
|
taxRate={taxRate}
|
|
266
85
|
size="sm"
|
|
267
86
|
t={t}
|
|
268
87
|
/>
|
|
269
88
|
</section>
|
|
89
|
+
|
|
270
90
|
<section className="min-w-0 overflow-visible rounded-lg border border-sky-200 bg-sky-50/50 p-3">
|
|
271
91
|
<div className="mb-3 flex items-center justify-between gap-3">
|
|
272
92
|
<div>
|
|
273
93
|
<h3 className="text-sm font-semibold text-stone-900">This change</h3>
|
|
274
|
-
<p className="mt-0.5 text-xs text-stone-500">
|
|
94
|
+
<p className="mt-0.5 text-xs text-stone-500">Server-priced amendment</p>
|
|
275
95
|
</div>
|
|
276
96
|
<span className="shrink-0 text-xs font-medium text-stone-500">
|
|
277
|
-
{formatCurrencyAmount(
|
|
97
|
+
{amountDue == null ? '—' : formatCurrencyAmount(amountDue, currency, displayLocale)}
|
|
278
98
|
</span>
|
|
279
99
|
</div>
|
|
280
|
-
{
|
|
100
|
+
{!quoteReady ? (
|
|
101
|
+
<div className="rounded-md border border-dashed border-stone-300 bg-white/70 px-3 py-4 text-center">
|
|
102
|
+
<p className="text-sm font-medium text-stone-700">Waiting for server price…</p>
|
|
103
|
+
</div>
|
|
104
|
+
) : amendmentLines.length === 0 && Math.abs(amountDue) < 0.005 ? (
|
|
281
105
|
<div className="rounded-md border border-dashed border-stone-300 bg-white/70 px-3 py-4 text-center">
|
|
282
106
|
<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
107
|
</div>
|
|
285
108
|
) : (
|
|
286
109
|
<PriceSummary
|
|
287
|
-
lines={
|
|
288
|
-
total={
|
|
110
|
+
lines={amendmentLines}
|
|
111
|
+
total={amountDue}
|
|
289
112
|
currency={currency}
|
|
290
113
|
locale={displayLocale}
|
|
291
|
-
subtotal={nonTaxChangeSubtotal}
|
|
292
|
-
taxAmount={!changeLinesIncludeTaxRow ? changeTax : 0}
|
|
293
|
-
taxRate={taxRate}
|
|
294
114
|
size="sm"
|
|
295
115
|
t={t}
|
|
296
116
|
/>
|
|
297
117
|
)}
|
|
298
118
|
</section>
|
|
299
119
|
</div>
|
|
300
|
-
|
|
301
|
-
|
|
120
|
+
|
|
121
|
+
{quoteReady ? (
|
|
122
|
+
<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
123
|
<div>
|
|
303
|
-
<h3 className="text-sm font-semibold text-stone-900">Updated booking
|
|
304
|
-
<p className="mt-0.5 text-xs text-stone-500">
|
|
124
|
+
<h3 className="text-sm font-semibold text-stone-900">Updated booking total</h3>
|
|
125
|
+
<p className="mt-0.5 text-xs text-stone-500">Authoritative total returned by the server quote</p>
|
|
305
126
|
</div>
|
|
306
|
-
<span className="
|
|
307
|
-
{formatCurrencyAmount(
|
|
127
|
+
<span className="text-lg font-semibold tabular-nums text-stone-900">
|
|
128
|
+
{formatCurrencyAmount(updatedTotal, currency, displayLocale)}
|
|
308
129
|
</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 ? (
|
|
130
|
+
</section>
|
|
131
|
+
) : null}
|
|
132
|
+
|
|
133
|
+
{adjustments ? (
|
|
330
134
|
<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}
|
|
135
|
+
<h3 className="mb-2 text-sm font-semibold text-stone-900">Admin adjustments</h3>
|
|
136
|
+
{adjustments}
|
|
336
137
|
</section>
|
|
337
138
|
) : null}
|
|
139
|
+
|
|
338
140
|
<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
141
|
<span className="text-sm font-semibold text-stone-900">{amountDueLabel}</span>
|
|
340
142
|
<span className={`text-lg font-semibold tabular-nums ${amountDueClass}`}>
|
|
341
|
-
{formatCurrencyAmount(
|
|
143
|
+
{amountDue == null ? '—' : formatCurrencyAmount(amountDue, currency, displayLocale)}
|
|
342
144
|
</span>
|
|
343
145
|
</div>
|
|
344
146
|
</div>
|