@ticketboothapp/booking 1.2.166 → 1.2.168
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 +1 -1
- package/src/components/booking/AdminChangeBookingContent.tsx +15 -1
- package/src/components/booking/AdminChangeBookingFlow.tsx +274 -94
- package/src/components/booking/AdminChangeCheckoutPanel.tsx +49 -13
- package/src/components/booking/AdminChangePricingPanel.tsx +400 -134
- package/src/components/booking/AdminChangePromoEditor.tsx +97 -0
- package/src/components/booking/AdminChangeReceiptComparison.tsx +172 -147
- package/src/components/booking/ChangeBookingFlow.tsx +6 -3
- package/src/components/booking/ChangeBookingQuoteStatusPlaceholder.tsx +7 -2
- package/src/components/booking/ChangeBookingSelectionControlsPanel.tsx +6 -0
- package/src/components/booking/ChangeBookingTicketsAndAddOnsPanel.tsx +80 -0
- package/src/components/booking/NewBookingFlow.tsx +14 -2
- package/src/components/booking/PickupLocationSelector.module.css +22 -0
- package/src/components/booking/PickupLocationSelector.tsx +3 -3
- package/src/components/booking/PriceBreakdown.tsx +66 -15
- package/src/components/booking/PriceSummary.tsx +16 -0
- package/src/components/booking/PrivateShuttleBookingFlow.tsx +52 -14
- package/src/components/booking/PrivateShuttleCheckoutSection.tsx +95 -54
- package/src/components/booking/admin-adjustment-components.ts +44 -0
- package/src/components/booking/admin-adjustment-tax-behavior.ts +50 -0
- package/src/components/booking/admin-change-flow-state-helpers.ts +10 -0
- package/src/components/booking/admin-change-provider-payload.ts +12 -1
- package/src/components/booking/admin-change-quote-request-key.ts +41 -1
- package/src/components/booking/admin-change-receipt-lines.ts +49 -0
- package/src/components/booking/admin-change-v2-quote-request.ts +109 -0
- package/src/components/booking/admin-refund-decision-context.ts +122 -0
- package/src/components/booking/admin-refund-disposition.ts +71 -2
- package/src/components/booking/availability-date-selection.ts +16 -0
- package/src/components/booking/booking-flow-types.ts +4 -0
- package/src/components/booking/booking-flow.css +5 -0
- package/src/components/booking/change-booking-error-message.ts +137 -0
- package/src/components/booking/change-booking-flow-helpers.ts +77 -2
- package/src/components/booking/change-booking-quote-guards.ts +4 -1
- package/src/components/booking/change-booking-quote-state.ts +44 -1
- package/src/components/booking/incompatible-add-on-selections.ts +19 -0
- package/src/components/booking/private-shuttle-availability.ts +14 -0
- package/src/components/booking/private-shuttle-cancellation-policy.ts +9 -0
- package/src/components/booking/private-shuttle-checkout-controller.ts +5 -0
- package/src/components/booking/private-shuttle-provider-change-runner.ts +21 -0
- package/src/components/booking/private-shuttle-reservation-runner.ts +5 -3
- package/src/components/booking/provider-dashboard-change-booking.ts +18 -2
- package/src/components/booking/use-standard-booking-availability.ts +5 -2
- package/src/components/booking/useAdminChangeCheckoutController.ts +56 -1
- package/src/components/booking/useAdminChangeProductReset.ts +6 -0
- package/src/components/booking/useAdminChangeProtectedPricing.ts +4 -0
- package/src/components/booking/useAdminChangeQuoteDisplayState.tsx +8 -6
- package/src/components/booking/useAdminChangeQuotePreview.ts +146 -61
- package/src/components/booking/useAdminCustomReceiptLines.ts +171 -10
- package/src/components/booking/useAdminProviderPricingAdjustments.ts +50 -19
- package/src/components/booking/useBookingAvailabilityAddOns.ts +26 -4
- package/src/components/booking/useBookingPromoAndQuantityController.ts +16 -2
- package/src/components/booking/useChangeBookingAddOnFloorController.ts +15 -9
- package/src/components/booking/useChangeBookingAutoSelections.ts +60 -17
- package/src/components/booking/useChangeBookingInitialHydration.ts +10 -7
- package/src/components/booking/useChangeBookingQuotePreview.ts +3 -1
- package/src/components/booking/useChangeBookingSelectionDetails.ts +23 -17
- package/src/components/booking/useStandardBookingAutoSelections.ts +55 -7
- package/src/lib/booking/booking-cutoffs.ts +22 -0
- package/src/lib/booking/change-booking-server-preview.ts +92 -5
- package/src/lib/booking/change-flow-pricing.ts +12 -0
- package/src/lib/booking/i18n/messages/en.json +1 -0
- package/src/lib/booking/i18n/messages/fr.json +1 -0
- package/src/lib/booking-api.ts +76 -1
- package/test/change-booking-helpers.test.ts +1319 -7
- package/src/components/booking/useAdminChangePricingDebugPanel.tsx +0 -178
|
@@ -1,24 +1,29 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useState } from "react";
|
|
2
2
|
import type {
|
|
3
3
|
ProviderDashboardChangePricingUi,
|
|
4
4
|
ProviderDashboardPricingLine,
|
|
5
|
-
} from
|
|
5
|
+
} from "./booking-flow-ui";
|
|
6
|
+
import type { AdminAdjustmentComponentOption } from "./admin-adjustment-components";
|
|
7
|
+
import {
|
|
8
|
+
adminAdjustmentTaxBehaviorOptions,
|
|
9
|
+
normalizeAdminAdjustmentScopeForSign,
|
|
10
|
+
normalizeAdminAdjustmentTaxBehavior,
|
|
11
|
+
} from "./admin-adjustment-tax-behavior";
|
|
12
|
+
import type { AdminCustomReceiptLine } from "./useAdminCustomReceiptLines";
|
|
6
13
|
|
|
7
|
-
export type AdminCustomReceiptLine
|
|
8
|
-
id: string;
|
|
9
|
-
label: string;
|
|
10
|
-
amountInput: string;
|
|
11
|
-
amountSign: 1 | -1;
|
|
12
|
-
calculation: 'FIXED_AMOUNT' | 'PERCENTAGE';
|
|
13
|
-
scope: 'POSITIVE_AMENDMENT_VALUE' | 'CURRENT_ACTIVE_TICKETS' | 'CURRENT_ACTIVE_BOOKING';
|
|
14
|
-
taxBehavior: 'NON_TAXABLE' | 'TAXABLE' | 'PRE_TAX_DISCOUNT' | 'POST_TAX_DISCOUNT';
|
|
15
|
-
reasonCode: string;
|
|
16
|
-
};
|
|
14
|
+
export type { AdminCustomReceiptLine } from "./useAdminCustomReceiptLines";
|
|
17
15
|
|
|
18
16
|
type AdminCustomReceiptLinePatch = Partial<
|
|
19
17
|
Pick<
|
|
20
18
|
AdminCustomReceiptLine,
|
|
21
|
-
|
|
19
|
+
| "label"
|
|
20
|
+
| "amountInput"
|
|
21
|
+
| "amountSign"
|
|
22
|
+
| "calculation"
|
|
23
|
+
| "scope"
|
|
24
|
+
| "selectedComponentIds"
|
|
25
|
+
| "taxBehavior"
|
|
26
|
+
| "reasonCode"
|
|
22
27
|
>
|
|
23
28
|
>;
|
|
24
29
|
|
|
@@ -26,31 +31,34 @@ export interface AdminChangePricingMessagesProps {
|
|
|
26
31
|
showProviderPricingInlineEditor: boolean;
|
|
27
32
|
providerPricingUi?: ProviderDashboardChangePricingUi;
|
|
28
33
|
providerQuotedLines: ProviderDashboardPricingLine[];
|
|
29
|
-
debugPanel: ReactNode;
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
export function AdminChangePricingMessages({
|
|
33
37
|
showProviderPricingInlineEditor,
|
|
34
38
|
providerPricingUi,
|
|
35
39
|
providerQuotedLines,
|
|
36
|
-
debugPanel,
|
|
37
40
|
}: AdminChangePricingMessagesProps) {
|
|
38
41
|
return (
|
|
39
42
|
<>
|
|
40
43
|
{showProviderPricingInlineEditor && providerPricingUi?.error ? (
|
|
41
|
-
<div className="mt-2 text-sm text-red-700">
|
|
44
|
+
<div className="mt-2 text-sm text-red-700">
|
|
45
|
+
{providerPricingUi.error}
|
|
46
|
+
</div>
|
|
42
47
|
) : null}
|
|
43
48
|
{showProviderPricingInlineEditor &&
|
|
44
49
|
providerPricingUi?.loading &&
|
|
45
50
|
providerQuotedLines.length === 0 ? (
|
|
46
|
-
<div className="mt-2 text-sm text-stone-500">
|
|
51
|
+
<div className="mt-2 text-sm text-stone-500">
|
|
52
|
+
Loading price lines...
|
|
53
|
+
</div>
|
|
47
54
|
) : null}
|
|
48
55
|
{showProviderPricingInlineEditor &&
|
|
49
56
|
providerPricingUi?.helperText &&
|
|
50
57
|
!providerPricingUi.error ? (
|
|
51
|
-
<div className="mt-2 text-xs text-stone-500">
|
|
58
|
+
<div className="mt-2 text-xs text-stone-500">
|
|
59
|
+
{providerPricingUi.helperText}
|
|
60
|
+
</div>
|
|
52
61
|
) : null}
|
|
53
|
-
{debugPanel}
|
|
54
62
|
</>
|
|
55
63
|
);
|
|
56
64
|
}
|
|
@@ -61,16 +69,38 @@ export interface AdminChangePricingAdjustmentsProps {
|
|
|
61
69
|
providerPricingUi?: ProviderDashboardChangePricingUi;
|
|
62
70
|
showAdminCustomLineEditor: boolean;
|
|
63
71
|
adminCustomReceiptLines: AdminCustomReceiptLine[];
|
|
64
|
-
|
|
65
|
-
|
|
72
|
+
hasPendingAdminCustomReceiptLineChanges: boolean;
|
|
73
|
+
pendingAdminCustomReceiptLineIds: string[];
|
|
74
|
+
adjustmentComponentOptions: AdminAdjustmentComponentOption[];
|
|
75
|
+
onAddAdminCustomReceiptLine: () => string;
|
|
76
|
+
onUpdateAdminCustomReceiptLine: (
|
|
77
|
+
id: string,
|
|
78
|
+
patch: AdminCustomReceiptLinePatch,
|
|
79
|
+
) => void;
|
|
66
80
|
onRemoveAdminCustomReceiptLine: (id: string) => void;
|
|
81
|
+
onApplyAdminCustomReceiptLines: () => void;
|
|
67
82
|
}
|
|
68
83
|
|
|
69
84
|
function normalizeAmountInput(value: string): string {
|
|
70
|
-
const digitsAndDots = value.replace(/[^0-9.]/g,
|
|
71
|
-
const parts = digitsAndDots.split(
|
|
85
|
+
const digitsAndDots = value.replace(/[^0-9.]/g, "");
|
|
86
|
+
const parts = digitsAndDots.split(".");
|
|
72
87
|
if (parts.length <= 1) return digitsAndDots;
|
|
73
|
-
return `${parts[0]}.${parts.slice(1).join(
|
|
88
|
+
return `${parts[0]}.${parts.slice(1).join("").replace(/\./g, "")}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function adminCustomLineScopeLabel(
|
|
92
|
+
scope: AdminCustomReceiptLine["scope"],
|
|
93
|
+
): string {
|
|
94
|
+
switch (scope) {
|
|
95
|
+
case "CURRENT_ACTIVE_TICKETS":
|
|
96
|
+
return "All tickets";
|
|
97
|
+
case "CURRENT_ACTIVE_BOOKING":
|
|
98
|
+
return "Whole booking";
|
|
99
|
+
case "SELECTED_COMPONENTS":
|
|
100
|
+
return "Choose specific items";
|
|
101
|
+
default:
|
|
102
|
+
return "Added by this change";
|
|
103
|
+
}
|
|
74
104
|
}
|
|
75
105
|
|
|
76
106
|
export function AdminChangePricingAdjustments({
|
|
@@ -79,13 +109,33 @@ export function AdminChangePricingAdjustments({
|
|
|
79
109
|
providerPricingUi,
|
|
80
110
|
showAdminCustomLineEditor,
|
|
81
111
|
adminCustomReceiptLines,
|
|
112
|
+
hasPendingAdminCustomReceiptLineChanges,
|
|
113
|
+
pendingAdminCustomReceiptLineIds,
|
|
114
|
+
adjustmentComponentOptions,
|
|
82
115
|
onAddAdminCustomReceiptLine,
|
|
83
116
|
onUpdateAdminCustomReceiptLine,
|
|
84
117
|
onRemoveAdminCustomReceiptLine,
|
|
118
|
+
onApplyAdminCustomReceiptLines,
|
|
85
119
|
}: AdminChangePricingAdjustmentsProps) {
|
|
86
|
-
|
|
120
|
+
const [
|
|
121
|
+
expandedAdminCustomReceiptLineIds,
|
|
122
|
+
setExpandedAdminCustomReceiptLineIds,
|
|
123
|
+
] = useState<string[]>([]);
|
|
124
|
+
if (!showChangeFlowManualPriceLines && !showAdminCustomLineEditor)
|
|
125
|
+
return null;
|
|
87
126
|
|
|
88
127
|
const providerAdjustments = providerPricingUi?.additionalAdjustments ?? [];
|
|
128
|
+
const hasIncompleteAdminCustomReceiptLine = adminCustomReceiptLines.some(
|
|
129
|
+
(line) => {
|
|
130
|
+
const amount = Number(line.amountInput.trim());
|
|
131
|
+
return (
|
|
132
|
+
!Number.isFinite(amount) ||
|
|
133
|
+
amount <= 0 ||
|
|
134
|
+
(line.scope === "SELECTED_COMPONENTS" &&
|
|
135
|
+
line.selectedComponentIds.length === 0)
|
|
136
|
+
);
|
|
137
|
+
},
|
|
138
|
+
);
|
|
89
139
|
|
|
90
140
|
return (
|
|
91
141
|
<>
|
|
@@ -94,12 +144,17 @@ export function AdminChangePricingAdjustments({
|
|
|
94
144
|
providerAdjustments.length > 0 ? (
|
|
95
145
|
<div className="space-y-1">
|
|
96
146
|
{providerAdjustments.map((adj) => (
|
|
97
|
-
<div
|
|
147
|
+
<div
|
|
148
|
+
key={adj.id}
|
|
149
|
+
className="flex items-center justify-between gap-2 text-sm"
|
|
150
|
+
>
|
|
98
151
|
<div className="flex min-w-0 items-center gap-1">
|
|
99
152
|
<button
|
|
100
153
|
type="button"
|
|
101
154
|
className="rounded border border-stone-300 px-1 text-xs text-stone-600 hover:bg-stone-100"
|
|
102
|
-
onClick={() =>
|
|
155
|
+
onClick={() =>
|
|
156
|
+
providerPricingUi?.onRemoveAdditionalAdjustment?.(adj.id)
|
|
157
|
+
}
|
|
103
158
|
>
|
|
104
159
|
-
|
|
105
160
|
</button>
|
|
@@ -109,7 +164,9 @@ export function AdminChangePricingAdjustments({
|
|
|
109
164
|
placeholder="Line description"
|
|
110
165
|
value={adj.label}
|
|
111
166
|
onChange={(e) =>
|
|
112
|
-
providerPricingUi?.onUpdateAdditionalAdjustment?.(adj.id, {
|
|
167
|
+
providerPricingUi?.onUpdateAdditionalAdjustment?.(adj.id, {
|
|
168
|
+
label: e.target.value,
|
|
169
|
+
})
|
|
113
170
|
}
|
|
114
171
|
/>
|
|
115
172
|
</div>
|
|
@@ -119,7 +176,7 @@ export function AdminChangePricingAdjustments({
|
|
|
119
176
|
value={adj.mode}
|
|
120
177
|
onChange={(e) =>
|
|
121
178
|
providerPricingUi?.onUpdateAdditionalAdjustment?.(adj.id, {
|
|
122
|
-
mode: e.target.value as
|
|
179
|
+
mode: e.target.value as "DISCOUNT" | "CHARGE",
|
|
123
180
|
})
|
|
124
181
|
}
|
|
125
182
|
>
|
|
@@ -160,13 +217,22 @@ export function AdminChangePricingAdjustments({
|
|
|
160
217
|
) : null}
|
|
161
218
|
{showAdminCustomLineEditor ? (
|
|
162
219
|
<div className="space-y-3">
|
|
163
|
-
<div className="flex justify-
|
|
220
|
+
<div className="flex justify-start">
|
|
164
221
|
<button
|
|
165
222
|
type="button"
|
|
166
223
|
className="admin-custom-receipt-line-add inline-flex items-center gap-1.5 rounded-full border border-dashed border-stone-300 bg-stone-50/80 text-xs font-medium text-stone-600 hover:border-stone-400 hover:bg-stone-100"
|
|
167
|
-
onClick={
|
|
224
|
+
onClick={() => {
|
|
225
|
+
const id = onAddAdminCustomReceiptLine();
|
|
226
|
+
setExpandedAdminCustomReceiptLineIds((current) => [
|
|
227
|
+
...current,
|
|
228
|
+
id,
|
|
229
|
+
]);
|
|
230
|
+
}}
|
|
168
231
|
>
|
|
169
|
-
<span
|
|
232
|
+
<span
|
|
233
|
+
className="text-base font-semibold leading-none text-stone-700"
|
|
234
|
+
aria-hidden
|
|
235
|
+
>
|
|
170
236
|
+
|
|
171
237
|
</span>
|
|
172
238
|
Add custom line
|
|
@@ -174,121 +240,321 @@ export function AdminChangePricingAdjustments({
|
|
|
174
240
|
</div>
|
|
175
241
|
{adminCustomReceiptLines.map((adj) => {
|
|
176
242
|
const sign = adj.amountSign ?? 1;
|
|
243
|
+
const isPending = pendingAdminCustomReceiptLineIds.includes(adj.id);
|
|
244
|
+
const isExpanded =
|
|
245
|
+
isPending || expandedAdminCustomReceiptLineIds.includes(adj.id);
|
|
246
|
+
const taxBehaviorLabel =
|
|
247
|
+
adminAdjustmentTaxBehaviorOptions(sign).find(
|
|
248
|
+
(option) => option.value === adj.taxBehavior,
|
|
249
|
+
)?.label ?? "Tax treatment";
|
|
250
|
+
const amountSummary = `${sign === -1 ? "\u2212" : "+"}${
|
|
251
|
+
adj.amountInput || "0.00"
|
|
252
|
+
}${adj.calculation === "PERCENTAGE" ? "%" : ""}`;
|
|
253
|
+
|
|
254
|
+
if (!isExpanded) {
|
|
255
|
+
return (
|
|
256
|
+
<div
|
|
257
|
+
key={adj.id}
|
|
258
|
+
className="admin-custom-receipt-line flex items-center justify-between gap-3 rounded-lg border border-stone-200 bg-stone-50/70 px-3 py-2.5 text-sm shadow-sm"
|
|
259
|
+
>
|
|
260
|
+
<div className="min-w-0">
|
|
261
|
+
<div className="truncate font-medium text-stone-800">
|
|
262
|
+
{adj.label.trim() || "Custom line"}
|
|
263
|
+
</div>
|
|
264
|
+
<div className="truncate text-xs text-stone-500">
|
|
265
|
+
{adminCustomLineScopeLabel(adj.scope)} ·{" "}
|
|
266
|
+
{taxBehaviorLabel}
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
<div className="flex shrink-0 items-center gap-2">
|
|
270
|
+
<span
|
|
271
|
+
className={`font-semibold tabular-nums ${
|
|
272
|
+
sign === -1 ? "text-red-700" : "text-emerald-700"
|
|
273
|
+
}`}
|
|
274
|
+
>
|
|
275
|
+
{amountSummary}
|
|
276
|
+
</span>
|
|
277
|
+
<button
|
|
278
|
+
type="button"
|
|
279
|
+
className="rounded-md px-2 py-1 text-xs font-medium text-stone-600 hover:bg-stone-200 hover:text-stone-800"
|
|
280
|
+
onClick={() =>
|
|
281
|
+
setExpandedAdminCustomReceiptLineIds((current) =>
|
|
282
|
+
current.includes(adj.id)
|
|
283
|
+
? current
|
|
284
|
+
: [...current, adj.id],
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
>
|
|
288
|
+
Edit
|
|
289
|
+
</button>
|
|
290
|
+
<button
|
|
291
|
+
type="button"
|
|
292
|
+
className="admin-custom-receipt-remove rounded-md text-stone-400 hover:bg-stone-200 hover:text-stone-700"
|
|
293
|
+
aria-label={`Remove ${adj.label.trim() || "custom line"}`}
|
|
294
|
+
onClick={() => onRemoveAdminCustomReceiptLine(adj.id)}
|
|
295
|
+
>
|
|
296
|
+
<span className="block text-xl leading-none" aria-hidden>
|
|
297
|
+
×
|
|
298
|
+
</span>
|
|
299
|
+
</button>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
177
305
|
return (
|
|
178
306
|
<div
|
|
179
307
|
key={adj.id}
|
|
180
|
-
className="admin-custom-receipt-line
|
|
308
|
+
className="admin-custom-receipt-line space-y-3 rounded-lg border border-stone-200 bg-stone-50/70 p-3 text-sm shadow-sm"
|
|
181
309
|
>
|
|
182
|
-
<
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
<select
|
|
192
|
-
className="rounded-md border border-stone-300 bg-white px-2 py-1 text-xs text-stone-700"
|
|
193
|
-
aria-label="Adjustment calculation"
|
|
194
|
-
value={adj.calculation}
|
|
195
|
-
onChange={(e) =>
|
|
196
|
-
onUpdateAdminCustomReceiptLine(adj.id, {
|
|
197
|
-
calculation: e.target.value as AdminCustomReceiptLine['calculation'],
|
|
198
|
-
})
|
|
199
|
-
}
|
|
200
|
-
>
|
|
201
|
-
<option value="FIXED_AMOUNT">Fixed amount</option>
|
|
202
|
-
<option value="PERCENTAGE">Percentage</option>
|
|
203
|
-
</select>
|
|
204
|
-
<div
|
|
205
|
-
className="flex shrink-0 rounded-lg border border-stone-300 bg-stone-100/90 p-0.5 shadow-sm"
|
|
206
|
-
role="group"
|
|
207
|
-
aria-label="Line adds to total or reduces it"
|
|
208
|
-
>
|
|
209
|
-
<button
|
|
210
|
-
type="button"
|
|
211
|
-
className={`admin-custom-receipt-segment rounded-md text-xs font-semibold transition-colors sm:text-sm ${
|
|
212
|
-
sign === 1
|
|
213
|
-
? 'bg-white text-emerald-800 shadow-sm ring-1 ring-stone-200/80'
|
|
214
|
-
: 'text-stone-500 hover:bg-stone-200/60 hover:text-stone-700'
|
|
215
|
-
}`}
|
|
216
|
-
aria-pressed={sign === 1}
|
|
217
|
-
onClick={() => onUpdateAdminCustomReceiptLine(adj.id, { amountSign: 1 })}
|
|
218
|
-
>
|
|
219
|
-
Charge (+)
|
|
220
|
-
</button>
|
|
310
|
+
<div className="flex items-center justify-between gap-3 border-b border-stone-200 pb-2">
|
|
311
|
+
<div className="text-xs font-semibold text-stone-600">
|
|
312
|
+
{adj.sourceAdjustmentId
|
|
313
|
+
? "Edit custom line"
|
|
314
|
+
: "New custom line"}
|
|
315
|
+
{adj.sourceAdjustmentId && adj.isDirty
|
|
316
|
+
? " · unapplied changes"
|
|
317
|
+
: ""}
|
|
318
|
+
</div>
|
|
221
319
|
<button
|
|
222
320
|
type="button"
|
|
223
|
-
className=
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
: 'text-stone-500 hover:bg-stone-200/60 hover:text-stone-700'
|
|
227
|
-
}`}
|
|
228
|
-
aria-pressed={sign === -1}
|
|
229
|
-
onClick={() => onUpdateAdminCustomReceiptLine(adj.id, { amountSign: -1 })}
|
|
321
|
+
className="admin-custom-receipt-remove shrink-0 rounded-md text-stone-400 hover:bg-stone-200 hover:text-stone-700"
|
|
322
|
+
aria-label={`Remove ${adj.label.trim() || "custom line"}`}
|
|
323
|
+
onClick={() => onRemoveAdminCustomReceiptLine(adj.id)}
|
|
230
324
|
>
|
|
231
|
-
|
|
325
|
+
<span className="block text-xl leading-none" aria-hidden>
|
|
326
|
+
×
|
|
327
|
+
</span>
|
|
232
328
|
</button>
|
|
233
329
|
</div>
|
|
234
|
-
<
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
<
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
330
|
+
<div className="flex flex-wrap items-center gap-2 sm:flex-nowrap">
|
|
331
|
+
<input
|
|
332
|
+
type="text"
|
|
333
|
+
className="admin-custom-receipt-input min-w-[8rem] flex-1 rounded-md border border-stone-300 bg-white text-sm text-stone-800 placeholder:text-stone-400"
|
|
334
|
+
placeholder="Label"
|
|
335
|
+
value={adj.label}
|
|
336
|
+
onChange={(e) =>
|
|
337
|
+
onUpdateAdminCustomReceiptLine(adj.id, {
|
|
338
|
+
label: e.target.value,
|
|
339
|
+
})
|
|
340
|
+
}
|
|
341
|
+
/>
|
|
342
|
+
<select
|
|
343
|
+
className="rounded-md border border-stone-300 bg-white px-2 py-1 text-xs text-stone-700"
|
|
344
|
+
aria-label="Adjustment calculation"
|
|
345
|
+
value={adj.calculation}
|
|
346
|
+
onChange={(e) =>
|
|
347
|
+
onUpdateAdminCustomReceiptLine(adj.id, {
|
|
348
|
+
calculation: e.target
|
|
349
|
+
.value as AdminCustomReceiptLine["calculation"],
|
|
350
|
+
})
|
|
351
|
+
}
|
|
352
|
+
>
|
|
353
|
+
<option value="FIXED_AMOUNT">Fixed amount</option>
|
|
354
|
+
<option value="PERCENTAGE">Percentage</option>
|
|
355
|
+
</select>
|
|
356
|
+
<div
|
|
357
|
+
className="flex shrink-0 rounded-lg border border-stone-300 bg-stone-100/90 p-0.5 shadow-sm"
|
|
358
|
+
role="group"
|
|
359
|
+
aria-label="Line adds to total or reduces it"
|
|
360
|
+
>
|
|
361
|
+
<button
|
|
362
|
+
type="button"
|
|
363
|
+
className={`admin-custom-receipt-segment rounded-md text-xs font-semibold transition-colors sm:text-sm ${
|
|
364
|
+
sign === 1
|
|
365
|
+
? "bg-white text-emerald-800 shadow-sm ring-1 ring-stone-200/80"
|
|
366
|
+
: "text-stone-500 hover:bg-stone-200/60 hover:text-stone-700"
|
|
367
|
+
}`}
|
|
368
|
+
aria-pressed={sign === 1}
|
|
369
|
+
onClick={() =>
|
|
370
|
+
onUpdateAdminCustomReceiptLine(adj.id, {
|
|
371
|
+
amountSign: 1,
|
|
372
|
+
taxBehavior: normalizeAdminAdjustmentTaxBehavior(
|
|
373
|
+
1,
|
|
374
|
+
adj.taxBehavior,
|
|
375
|
+
),
|
|
376
|
+
})
|
|
377
|
+
}
|
|
378
|
+
>
|
|
379
|
+
Charge (+)
|
|
380
|
+
</button>
|
|
381
|
+
<button
|
|
382
|
+
type="button"
|
|
383
|
+
className={`admin-custom-receipt-segment rounded-md text-xs font-semibold transition-colors sm:text-sm ${
|
|
384
|
+
sign === -1
|
|
385
|
+
? "bg-white text-red-800 shadow-sm ring-1 ring-stone-200/80"
|
|
386
|
+
: "text-stone-500 hover:bg-stone-200/60 hover:text-stone-700"
|
|
387
|
+
}`}
|
|
388
|
+
aria-pressed={sign === -1}
|
|
389
|
+
onClick={() =>
|
|
390
|
+
onUpdateAdminCustomReceiptLine(adj.id, {
|
|
391
|
+
amountSign: -1,
|
|
392
|
+
scope: normalizeAdminAdjustmentScopeForSign(
|
|
393
|
+
-1,
|
|
394
|
+
adj.scope,
|
|
395
|
+
),
|
|
396
|
+
taxBehavior: normalizeAdminAdjustmentTaxBehavior(
|
|
397
|
+
-1,
|
|
398
|
+
adj.taxBehavior,
|
|
399
|
+
),
|
|
400
|
+
})
|
|
401
|
+
}
|
|
402
|
+
>
|
|
403
|
+
Credit (−)
|
|
404
|
+
</button>
|
|
405
|
+
</div>
|
|
406
|
+
<input
|
|
407
|
+
type="text"
|
|
408
|
+
inputMode="decimal"
|
|
409
|
+
className="admin-custom-receipt-input-amount w-[7rem] shrink-0 rounded-md border border-stone-300 bg-white text-right text-sm font-medium tabular-nums text-stone-800 placeholder:text-stone-400 focus:outline-none focus:ring-2 focus:ring-stone-400/80"
|
|
410
|
+
placeholder={
|
|
411
|
+
adj.calculation === "PERCENTAGE" ? "10" : "0.00"
|
|
412
|
+
}
|
|
413
|
+
aria-label="Amount"
|
|
414
|
+
value={adj.amountInput}
|
|
415
|
+
onChange={(e) =>
|
|
416
|
+
onUpdateAdminCustomReceiptLine(adj.id, {
|
|
417
|
+
amountInput: normalizeAmountInput(e.target.value),
|
|
418
|
+
})
|
|
419
|
+
}
|
|
420
|
+
/>
|
|
421
|
+
<span className="text-xs text-stone-500" aria-hidden>
|
|
422
|
+
{adj.calculation === "PERCENTAGE" ? "%" : ""}
|
|
287
423
|
</span>
|
|
288
|
-
</
|
|
424
|
+
</div>
|
|
425
|
+
<div className="flex flex-wrap items-center justify-end gap-2">
|
|
426
|
+
{adj.calculation === "PERCENTAGE" || sign === -1 ? (
|
|
427
|
+
<select
|
|
428
|
+
className="rounded-md border border-stone-300 bg-white px-2 py-1 text-xs text-stone-700"
|
|
429
|
+
aria-label="Applies to"
|
|
430
|
+
value={adj.scope}
|
|
431
|
+
onChange={(e) =>
|
|
432
|
+
onUpdateAdminCustomReceiptLine(adj.id, {
|
|
433
|
+
scope: e.target
|
|
434
|
+
.value as AdminCustomReceiptLine["scope"],
|
|
435
|
+
...(e.target.value === "SELECTED_COMPONENTS"
|
|
436
|
+
? {}
|
|
437
|
+
: { selectedComponentIds: [] }),
|
|
438
|
+
})
|
|
439
|
+
}
|
|
440
|
+
>
|
|
441
|
+
<option value="POSITIVE_AMENDMENT_VALUE">
|
|
442
|
+
Added by this change
|
|
443
|
+
</option>
|
|
444
|
+
<option value="CURRENT_ACTIVE_TICKETS">
|
|
445
|
+
All tickets
|
|
446
|
+
</option>
|
|
447
|
+
<option value="CURRENT_ACTIVE_BOOKING">
|
|
448
|
+
Whole booking
|
|
449
|
+
</option>
|
|
450
|
+
<option value="SELECTED_COMPONENTS">
|
|
451
|
+
Choose specific items
|
|
452
|
+
</option>
|
|
453
|
+
</select>
|
|
454
|
+
) : null}
|
|
455
|
+
<select
|
|
456
|
+
className="rounded-md border border-stone-300 bg-white px-2 py-1 text-xs text-stone-700"
|
|
457
|
+
aria-label="Tax treatment"
|
|
458
|
+
value={adj.taxBehavior}
|
|
459
|
+
onChange={(e) =>
|
|
460
|
+
onUpdateAdminCustomReceiptLine(adj.id, {
|
|
461
|
+
taxBehavior: e.target
|
|
462
|
+
.value as AdminCustomReceiptLine["taxBehavior"],
|
|
463
|
+
})
|
|
464
|
+
}
|
|
465
|
+
>
|
|
466
|
+
{adminAdjustmentTaxBehaviorOptions(sign).map((option) => (
|
|
467
|
+
<option key={option.value} value={option.value}>
|
|
468
|
+
{option.label}
|
|
469
|
+
</option>
|
|
470
|
+
))}
|
|
471
|
+
</select>
|
|
472
|
+
</div>
|
|
473
|
+
{sign === -1 && adj.scope === "POSITIVE_AMENDMENT_VALUE" ? (
|
|
474
|
+
<p className="text-xs text-stone-500">
|
|
475
|
+
Use this scope only when this change adds priced value, such
|
|
476
|
+
as another ticket or a tour upgrade. Otherwise choose active
|
|
477
|
+
tickets, the entire booking, or specific components.
|
|
478
|
+
</p>
|
|
479
|
+
) : null}
|
|
480
|
+
{adj.scope === "SELECTED_COMPONENTS" ? (
|
|
481
|
+
<fieldset className="rounded-md border border-stone-200 bg-stone-50 px-3 py-2">
|
|
482
|
+
<legend className="px-1 text-xs font-semibold text-stone-700">
|
|
483
|
+
Apply this line to
|
|
484
|
+
</legend>
|
|
485
|
+
{adjustmentComponentOptions.length > 0 ? (
|
|
486
|
+
<div className="grid gap-2 sm:grid-cols-2">
|
|
487
|
+
{adjustmentComponentOptions.map((component) => {
|
|
488
|
+
const checked = adj.selectedComponentIds.includes(
|
|
489
|
+
component.componentId,
|
|
490
|
+
);
|
|
491
|
+
return (
|
|
492
|
+
<label
|
|
493
|
+
key={component.componentId}
|
|
494
|
+
className="flex cursor-pointer items-start gap-2 rounded border border-stone-200 bg-white px-2 py-1.5 text-xs text-stone-700"
|
|
495
|
+
>
|
|
496
|
+
<input
|
|
497
|
+
type="checkbox"
|
|
498
|
+
className="mt-0.5"
|
|
499
|
+
checked={checked}
|
|
500
|
+
onChange={() =>
|
|
501
|
+
onUpdateAdminCustomReceiptLine(adj.id, {
|
|
502
|
+
selectedComponentIds: checked
|
|
503
|
+
? adj.selectedComponentIds.filter(
|
|
504
|
+
(id) => id !== component.componentId,
|
|
505
|
+
)
|
|
506
|
+
: [
|
|
507
|
+
...adj.selectedComponentIds,
|
|
508
|
+
component.componentId,
|
|
509
|
+
],
|
|
510
|
+
})
|
|
511
|
+
}
|
|
512
|
+
/>
|
|
513
|
+
<span className="min-w-0">
|
|
514
|
+
<span className="block font-medium">
|
|
515
|
+
{component.label}
|
|
516
|
+
</span>
|
|
517
|
+
<span className="block text-stone-500">
|
|
518
|
+
{component.type}
|
|
519
|
+
</span>
|
|
520
|
+
</span>
|
|
521
|
+
</label>
|
|
522
|
+
);
|
|
523
|
+
})}
|
|
524
|
+
</div>
|
|
525
|
+
) : (
|
|
526
|
+
<p className="text-xs text-stone-500">
|
|
527
|
+
Loading eligible booking components…
|
|
528
|
+
</p>
|
|
529
|
+
)}
|
|
530
|
+
{adj.selectedComponentIds.length === 0 ? (
|
|
531
|
+
<p className="mt-2 text-xs font-medium text-red-700">
|
|
532
|
+
Choose at least one booking component.
|
|
533
|
+
</p>
|
|
534
|
+
) : null}
|
|
535
|
+
</fieldset>
|
|
536
|
+
) : null}
|
|
289
537
|
</div>
|
|
290
538
|
);
|
|
291
539
|
})}
|
|
540
|
+
{hasPendingAdminCustomReceiptLineChanges ? (
|
|
541
|
+
<div className="flex justify-center border-t border-stone-200 px-4 py-4">
|
|
542
|
+
<button
|
|
543
|
+
type="button"
|
|
544
|
+
className="admin-custom-receipt-apply rounded-md bg-emerald-700 text-sm font-semibold text-white shadow-sm hover:bg-emerald-800 disabled:cursor-not-allowed disabled:bg-stone-300 disabled:shadow-none"
|
|
545
|
+
disabled={
|
|
546
|
+
!hasPendingAdminCustomReceiptLineChanges ||
|
|
547
|
+
hasIncompleteAdminCustomReceiptLine
|
|
548
|
+
}
|
|
549
|
+
onClick={() => {
|
|
550
|
+
onApplyAdminCustomReceiptLines();
|
|
551
|
+
setExpandedAdminCustomReceiptLineIds([]);
|
|
552
|
+
}}
|
|
553
|
+
>
|
|
554
|
+
Apply custom line changes
|
|
555
|
+
</button>
|
|
556
|
+
</div>
|
|
557
|
+
) : null}
|
|
292
558
|
</div>
|
|
293
559
|
) : null}
|
|
294
560
|
</>
|