PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
yatra / assets / dist / js / DynamicPricingRuleForm-C99aT0Zu.js

DynamicPricingRuleForm-C99aT0Zu.js in Yatra – Travel Booking & Tour Operator Software 3.0.16, at assets/dist/js/DynamicPricingRuleForm-C99aT0Zu.js

1,130 lines 62.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { v as useQueryClient, r as reactExports, u as useQuery, w as useMutation, j as jsxRuntimeExports, q as Calendar, as as Clock, T as TrendingUp, h as Package, H as Sun, c8 as Target, a6 as React, aB as Check, E as Loader2, aW as Save, D as ArrowLeft } from "./react-vendor-CithbthY.js";
2 import { P as PageHeader, C as Card, d as CardContent, B as Button, X as Skeleton, f as CardHeader, g as CardTitle, h as CardDescription, s as Label, S as Select, D as DatePicker } from "../../admin/dist/js/app.js";
3 import { u as useToast, _ as __, I as Input, a as apiClient } from "./index-Dec_weLB.js";
4 import { R as RuleTypeSelectionModal } from "./RuleTypeSelectionModal-_WAr6JyA.js";
5 const RULE_TYPES = [
6 {
7 id: "early_bird",
8 name: __("Early Bird Discount"),
9 description: __(
10 "Reward customers who book well in advance. Always behaves as a discount."
11 ),
12 icon: Calendar,
13 color: "blue",
14 example: __(
15 "10% off when departure is at least 30 days away (set Min Days Before = 30)."
16 )
17 },
18 {
19 id: "last_minute",
20 name: __("Last Minute Deals"),
21 description: __(
22 "Discount close to departure to fill remaining seats. Always behaves as a discount."
23 ),
24 icon: Clock,
25 color: "orange",
26 example: __(
27 "15% off when departure is within 7 days (set Max Days Before = 7)."
28 )
29 },
30 {
31 id: "demand",
32 name: __("Demand-Based Pricing"),
33 description: __(
34 "Adjusts price by booking velocity score (recomputed by cron / on each booking)."
35 ),
36 icon: TrendingUp,
37 color: "green",
38 example: __(
39 "Score above 70 → markup; below 30 → discount. Magnitude scales with how far the score is outside the band."
40 )
41 },
42 {
43 id: "inventory",
44 name: __("Inventory-Based"),
45 description: __(
46 "Reacts to seats remaining on the chosen departure (works with time slots too)."
47 ),
48 icon: Package,
49 color: "purple",
50 example: __(
51 "Seats ≤ 5 → +10% scarcity markup; seats ≥ 20 → 50% of magnitude as discount."
52 )
53 },
54 {
55 id: "seasonal",
56 name: __("Seasonal Pricing"),
57 description: __(
58 "Applies when the DEPARTURE date falls within the season window."
59 ),
60 icon: Sun,
61 color: "yellow",
62 example: __(
63 "+25% on departures between Jul 1 – Aug 31. Use a negative value for off-peak discounts."
64 )
65 },
66 {
67 id: "time_based",
68 name: __("Time-Based (Weekday/Weekend)"),
69 description: __(
70 "Applies on selected weekdays of the DEPARTURE date, optionally limited to a date window."
71 ),
72 icon: Target,
73 color: "indigo",
74 example: __(
75 "+15% premium on Saturday & Sunday departures. Add a date range to make it effective only for that period."
76 )
77 }
78 ];
79 const DynamicPricingRuleForm = () => {
80 const { showToast } = useToast();
81 const queryClient = useQueryClient();
82 const urlParams = new URLSearchParams(window.location.search);
83 const ruleId = urlParams.get("id");
84 const ruleTypeFromUrl = urlParams.get("rule_type") || "";
85 const isEdit = !!ruleId;
86 const [formData, setFormData] = reactExports.useState({
87 name: "",
88 rule_type: ruleTypeFromUrl,
89 adjustment_type: "percentage",
90 adjustment_value: 0,
91 min_days_before: 0,
92 max_days_before: 0,
93 min_inventory: 0,
94 max_inventory: 0,
95 start_date: "",
96 end_date: "",
97 applicable_trips: "all",
98 trip_ids: [],
99 status: "active",
100 priority: 1,
101 demand_threshold_high: 80,
102 demand_threshold_low: 30,
103 weekend_days: []
104 });
105 const [showTripDropdown, setShowTripDropdown] = reactExports.useState(false);
106 const [tripSearchQuery, setTripSearchQuery] = reactExports.useState("");
107 const [debouncedTripSearch, setDebouncedTripSearch] = reactExports.useState("");
108 const [showRuleTypeModal, setShowRuleTypeModal] = reactExports.useState(false);
109 const [previewBasePrice, setPreviewBasePrice] = reactExports.useState(100);
110 const [previewLoading, setPreviewLoading] = reactExports.useState(false);
111 const [previewError, setPreviewError] = reactExports.useState(null);
112 const [previewResult, setPreviewResult] = reactExports.useState(null);
113 const handleSelectRuleType = (ruleType) => {
114 handleChange("rule_type", ruleType);
115 setShowRuleTypeModal(false);
116 setPreviewResult(null);
117 setPreviewError(null);
118 };
119 const handleRunSimulation = async () => {
120 if (!formData.rule_type) {
121 setPreviewError(__("Pick a rule type first."));
122 return;
123 }
124 setPreviewLoading(true);
125 setPreviewError(null);
126 try {
127 const response = await apiClient.post("/dynamic-pricing/simulate-rule", {
128 rule_type: formData.rule_type,
129 adjustment_type: formData.adjustment_type,
130 adjustment_value: formData.adjustment_value,
131 min_days_before: formData.min_days_before,
132 max_days_before: formData.max_days_before,
133 min_inventory: formData.min_inventory,
134 max_inventory: formData.max_inventory,
135 start_date: formData.start_date || null,
136 end_date: formData.end_date || null,
137 weekend_days: formData.weekend_days || [],
138 demand_threshold_low: formData.demand_threshold_low,
139 demand_threshold_high: formData.demand_threshold_high,
140 base_price: previewBasePrice > 0 ? previewBasePrice : 100
141 });
142 const envelope = response;
143 if ((envelope == null ? void 0 : envelope.success) === false) {
144 throw new Error((envelope == null ? void 0 : envelope.message) || __("Simulation failed."));
145 }
146 const result = (envelope == null ? void 0 : envelope.data) ?? envelope;
147 if (!result || !Array.isArray(result.scenarios)) {
148 throw new Error(__("Simulation returned an unexpected response."));
149 }
150 setPreviewResult(result);
151 } catch (e) {
152 setPreviewError((e == null ? void 0 : e.message) || __("Simulation failed."));
153 } finally {
154 setPreviewLoading(false);
155 }
156 };
157 reactExports.useEffect(() => {
158 const timer = setTimeout(() => {
159 setDebouncedTripSearch(tripSearchQuery);
160 }, 300);
161 return () => clearTimeout(timer);
162 }, [tripSearchQuery]);
163 const { data: tripsData } = useQuery({
164 queryKey: ["trips-list", debouncedTripSearch],
165 queryFn: async () => {
166 const params = new URLSearchParams();
167 if (debouncedTripSearch) {
168 params.append("search", debouncedTripSearch);
169 }
170 params.append("per_page", "50");
171 const response = await apiClient.get(`/trips?${params.toString()}`);
172 return response.data || [];
173 }
174 });
175 const tripOptions = (tripsData || []).map((trip) => ({
176 value: trip.id,
177 label: trip.title || `Trip #${trip.id}`
178 }));
179 const {
180 data: ruleData,
181 isLoading,
182 isError,
183 error
184 } = useQuery({
185 queryKey: ["dynamic-pricing-rule", ruleId],
186 queryFn: async () => {
187 if (!ruleId) return null;
188 const response = await apiClient.get(`/dynamic-pricing/rules/${ruleId}`);
189 return response;
190 },
191 enabled: isEdit,
192 retry: 1
193 });
194 reactExports.useEffect(() => {
195 if (ruleData == null ? void 0 : ruleData.data) {
196 const ruleDetails = ruleData.data;
197 setFormData({
198 name: ruleDetails.name || "",
199 rule_type: ruleDetails.rule_type || "",
200 adjustment_type: ruleDetails.adjustment_type || "percentage",
201 adjustment_value: Number(ruleDetails.adjustment_value) || 0,
202 min_days_before: Number(ruleDetails.min_days_before) || 0,
203 max_days_before: Number(ruleDetails.max_days_before) || 0,
204 min_inventory: Number(ruleDetails.min_inventory) || 0,
205 max_inventory: Number(ruleDetails.max_inventory) || 0,
206 start_date: ruleDetails.start_date || "",
207 end_date: ruleDetails.end_date || "",
208 applicable_trips: ruleDetails.applicable_trips || "all",
209 trip_ids: Array.isArray(ruleDetails.trip_ids) ? ruleDetails.trip_ids : ruleDetails.trip_ids ? JSON.parse(ruleDetails.trip_ids) : [],
210 status: ruleDetails.status || "active",
211 priority: Number(ruleDetails.priority) || 1,
212 demand_threshold_high: ruleDetails.demand_threshold_high ? Number(ruleDetails.demand_threshold_high) : 80,
213 demand_threshold_low: ruleDetails.demand_threshold_low ? Number(ruleDetails.demand_threshold_low) : 30,
214 weekend_days: Array.isArray(ruleDetails.weekend_days) ? ruleDetails.weekend_days : ruleDetails.weekend_days ? JSON.parse(ruleDetails.weekend_days) : []
215 });
216 }
217 }, [ruleData]);
218 const saveMutation = useMutation({
219 mutationFn: async (data) => {
220 if (isEdit) {
221 return await apiClient.put(`/dynamic-pricing/rules/${ruleId}`, data);
222 } else {
223 return await apiClient.post("/dynamic-pricing/rules", data);
224 }
225 },
226 onSuccess: (response) => {
227 var _a;
228 showToast(
229 isEdit ? __("Pricing rule updated successfully") : __("Pricing rule created successfully"),
230 "success"
231 );
232 queryClient.invalidateQueries({ queryKey: ["dynamic-pricing-rules"] });
233 if (!isEdit && ((_a = response == null ? void 0 : response.data) == null ? void 0 : _a.id)) {
234 const baseUrl = window.location.href.split("&action=")[0];
235 const editUrl = `${baseUrl}&action=edit-pricing-rule&id=${response.data.id}`;
236 window.location.href = editUrl;
237 }
238 },
239 onError: (error2) => {
240 showToast((error2 == null ? void 0 : error2.message) || __("Failed to save pricing rule"), "error");
241 }
242 });
243 const handleSubmit = (e) => {
244 e.preventDefault();
245 saveMutation.mutate(formData);
246 };
247 const handleCancel = () => {
248 window.location.href = window.location.href.split("&action=")[0];
249 };
250 const handleChange = (field, value) => {
251 setFormData((prev) => ({ ...prev, [field]: value }));
252 };
253 if (isError) {
254 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
255 /* @__PURE__ */ jsxRuntimeExports.jsx(
256 PageHeader,
257 {
258 title: __("Edit Pricing Rule"),
259 description: __(
260 "Configure dynamic pricing rules to automatically adjust trip prices"
261 )
262 }
263 ),
264 /* @__PURE__ */ jsxRuntimeExports.jsx(Card, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { className: "pt-6", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center py-12", children: [
265 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-red-600 dark:text-red-400 mb-4", children: __("Failed to load pricing rule") }),
266 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400 mb-4", children: (error == null ? void 0 : error.message) || __("An error occurred while fetching the rule data") }),
267 /* @__PURE__ */ jsxRuntimeExports.jsx(
268 Button,
269 {
270 onClick: () => window.location.href = window.location.href.split("&action=")[0],
271 children: __("Back to Rules")
272 }
273 )
274 ] }) }) })
275 ] });
276 }
277 if (isLoading) {
278 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
279 /* @__PURE__ */ jsxRuntimeExports.jsx(
280 PageHeader,
281 {
282 title: __("Edit Pricing Rule"),
283 description: __(
284 "Configure dynamic pricing rules to automatically adjust trip prices"
285 )
286 }
287 ),
288 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-3 gap-6", children: [
289 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lg:col-span-2 space-y-6", children: [
290 /* @__PURE__ */ jsxRuntimeExports.jsx(Card, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { className: "pt-6", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
291 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "w-5 h-5 rounded" }),
292 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
293 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-3 w-16 mb-2" }),
294 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-5 w-48 mb-1" }),
295 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-3 w-64" })
296 ] })
297 ] }) }) }),
298 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
299 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
300 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-6 w-32 mb-2" }),
301 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-4 w-64" })
302 ] }),
303 /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { className: "space-y-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
304 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-4 w-24 mb-2" }),
305 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-10 w-full" })
306 ] }) })
307 ] }),
308 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
309 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
310 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-6 w-40 mb-2" }),
311 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-4 w-48" })
312 ] }),
313 /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { className: "space-y-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
314 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
315 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-4 w-32 mb-2" }),
316 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-10 w-full" })
317 ] }),
318 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
319 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-4 w-24 mb-2" }),
320 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-10 w-full" })
321 ] })
322 ] }) })
323 ] }),
324 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
325 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
326 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-6 w-32 mb-2" }),
327 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-4 w-56" })
328 ] }),
329 /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { className: "space-y-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
330 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
331 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-4 w-32 mb-2" }),
332 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-10 w-full" })
333 ] }),
334 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
335 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-4 w-32 mb-2" }),
336 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-10 w-full" })
337 ] })
338 ] }) })
339 ] }),
340 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
341 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
342 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-6 w-40 mb-2" }),
343 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-4 w-64" })
344 ] }),
345 /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { className: "space-y-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
346 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-4 w-32 mb-2" }),
347 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-10 w-full" })
348 ] }) })
349 ] })
350 ] }),
351 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
352 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
353 /* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-6 w-32 mb-2" }) }),
354 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-4", children: [
355 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
356 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-4 w-16 mb-2" }),
357 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-10 w-full" })
358 ] }),
359 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
360 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-4 w-16 mb-2" }),
361 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-10 w-full" })
362 ] })
363 ] })
364 ] }),
365 /* @__PURE__ */ jsxRuntimeExports.jsx(Card, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "pt-6 space-y-3", children: [
366 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-10 w-full" }),
367 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-10 w-full" })
368 ] }) })
369 ] })
370 ] })
371 ] });
372 }
373 const selectedRuleType = RULE_TYPES.find(
374 (rt) => rt.id === formData.rule_type
375 );
376 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
377 /* @__PURE__ */ jsxRuntimeExports.jsx(
378 PageHeader,
379 {
380 title: isEdit ? __("Edit Pricing Rule") : __("Create Pricing Rule"),
381 description: __(
382 "Configure dynamic pricing rules to automatically adjust trip prices"
383 )
384 }
385 ),
386 /* @__PURE__ */ jsxRuntimeExports.jsxs("form", { onSubmit: handleSubmit, children: [
387 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-3 gap-6", children: [
388 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lg:col-span-2 space-y-6", children: [
389 !isEdit && !formData.rule_type && /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
390 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
391 /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { children: __("Step 1: Select Rule Type") }),
392 /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: __(
393 "Choose the type of dynamic pricing rule you want to create"
394 ) })
395 ] }),
396 /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: RULE_TYPES.map((ruleType) => {
397 const Icon = ruleType.icon;
398 const colorClasses = {
399 blue: "border-blue-200 dark:border-blue-800 hover:border-blue-400 dark:hover:border-blue-600 bg-blue-50 dark:bg-blue-900/20",
400 orange: "border-orange-200 dark:border-orange-800 hover:border-orange-400 dark:hover:border-orange-600 bg-orange-50 dark:bg-orange-900/20",
401 green: "border-green-200 dark:border-green-800 hover:border-green-400 dark:hover:border-green-600 bg-green-50 dark:bg-green-900/20",
402 purple: "border-purple-200 dark:border-purple-800 hover:border-purple-400 dark:hover:border-purple-600 bg-purple-50 dark:bg-purple-900/20",
403 yellow: "border-yellow-200 dark:border-yellow-800 hover:border-yellow-400 dark:hover:border-yellow-600 bg-yellow-50 dark:bg-yellow-900/20",
404 indigo: "border-indigo-200 dark:border-indigo-800 hover:border-indigo-400 dark:hover:border-indigo-600 bg-indigo-50 dark:bg-indigo-900/20"
405 };
406 return /* @__PURE__ */ jsxRuntimeExports.jsx(
407 "button",
408 {
409 type: "button",
410 onClick: () => handleChange("rule_type", ruleType.id),
411 className: `p-4 border-2 rounded-lg text-left transition-all cursor-pointer ${colorClasses[ruleType.color]}`,
412 children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-3", children: [
413 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { className: "w-6 h-6 text-gray-700 dark:text-gray-300" }) }),
414 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
415 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-semibold text-gray-900 dark:text-white mb-1", children: ruleType.name }),
416 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-2", children: ruleType.description }),
417 /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-gray-500 dark:text-gray-500 italic", children: [
418 __("Example:"),
419 " ",
420 ruleType.example
421 ] })
422 ] })
423 ] })
424 },
425 ruleType.id
426 );
427 }) }) })
428 ] }),
429 (isEdit || formData.rule_type) && selectedRuleType && /* @__PURE__ */ jsxRuntimeExports.jsx(Card, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { className: "pt-6", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
430 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
431 React.createElement(selectedRuleType.icon, {
432 className: "w-5 h-5 text-gray-600 dark:text-gray-400"
433 }),
434 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
435 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: __("Rule Type") }),
436 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "font-semibold text-gray-900 dark:text-white", children: selectedRuleType.name }),
437 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: selectedRuleType.description })
438 ] })
439 ] }),
440 /* @__PURE__ */ jsxRuntimeExports.jsx(
441 Button,
442 {
443 type: "button",
444 variant: "outline",
445 size: "sm",
446 onClick: () => setShowRuleTypeModal(true),
447 children: __("Change")
448 }
449 )
450 ] }) }) }),
451 formData.rule_type && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
452 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
453 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
454 /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { children: __("Rule Details") }),
455 /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: __(
456 "Internal name for this pricing rule (visible to admins only)."
457 ) })
458 ] }),
459 /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { className: "space-y-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
460 /* @__PURE__ */ jsxRuntimeExports.jsxs(Label, { htmlFor: "name", children: [
461 __("Rule Name"),
462 " *"
463 ] }),
464 /* @__PURE__ */ jsxRuntimeExports.jsx(
465 Input,
466 {
467 id: "name",
468 value: formData.name,
469 onChange: (e) => handleChange("name", e.target.value),
470 placeholder: __("e.g., Summer Early Bird Discount"),
471 required: true
472 }
473 ),
474 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: __(
475 "Used in admin lists, analytics, and price-history exports. Customers never see this name."
476 ) })
477 ] }) })
478 ] }),
479 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
480 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
481 /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { children: __("Price Adjustment") }),
482 /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: __("How much to adjust the price") })
483 ] }),
484 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-4", children: [
485 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
486 /* @__PURE__ */ jsxRuntimeExports.jsxs(Label, { htmlFor: "adjustment_type", children: [
487 __("Adjustment Type"),
488 " *"
489 ] }),
490 /* @__PURE__ */ jsxRuntimeExports.jsxs(
491 Select,
492 {
493 id: "adjustment_type",
494 value: formData.adjustment_type,
495 onChange: (e) => handleChange("adjustment_type", e.target.value),
496 required: true,
497 children: [
498 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "percentage", children: __("Percentage") }),
499 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "fixed", children: __("Fixed Amount") })
500 ]
501 }
502 )
503 ] }),
504 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
505 /* @__PURE__ */ jsxRuntimeExports.jsxs(Label, { htmlFor: "adjustment_value", children: [
506 __("Adjustment Value"),
507 " *"
508 ] }),
509 /* @__PURE__ */ jsxRuntimeExports.jsx(
510 Input,
511 {
512 id: "adjustment_value",
513 type: "number",
514 step: "0.01",
515 value: formData.adjustment_value,
516 onChange: (e) => handleChange(
517 "adjustment_value",
518 parseFloat(e.target.value)
519 ),
520 placeholder: formData.adjustment_type === "percentage" ? "10" : "100",
521 required: true
522 }
523 ),
524 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: (() => {
525 const rt = formData.rule_type;
526 if (rt === "early_bird" || rt === "last_minute") {
527 return formData.adjustment_type === "percentage" ? __(
528 "Enter the discount magnitude as a positive number (e.g. 10 = 10% off). The rule always applies as a discount."
529 ) : __(
530 "Enter the discount magnitude as a positive number (e.g. 100 = 100 off). The rule always applies as a discount."
531 );
532 }
533 if (rt === "inventory" || rt === "demand") {
534 return formData.adjustment_type === "percentage" ? __(
535 "Enter the magnitude (positive). Direction is derived automatically: scarce inventory / high demand → markup, plenty / low demand → discount."
536 ) : __(
537 "Enter the magnitude (positive). Direction is derived automatically: scarce inventory / high demand → markup, plenty / low demand → discount."
538 );
539 }
540 return formData.adjustment_type === "percentage" ? __(
541 "Use a positive value to increase price (e.g. 10 = +10%) and a negative value to discount (e.g. -10 = 10% off)."
542 ) : __(
543 "Use a positive value to increase price (e.g. 100 = +100) and a negative value to discount (e.g. -100 = 100 off)."
544 );
545 })() })
546 ] })
547 ] })
548 ] }),
549 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
550 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
551 /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { children: __("Conditions") }),
552 /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: __("When this rule should apply") })
553 ] }),
554 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-4", children: [
555 (formData.rule_type === "early_bird" || formData.rule_type === "last_minute") && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
556 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
557 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "min_days_before", children: __("Minimum Days Before Departure") }),
558 /* @__PURE__ */ jsxRuntimeExports.jsx(
559 Input,
560 {
561 id: "min_days_before",
562 type: "number",
563 value: formData.min_days_before,
564 onChange: (e) => handleChange(
565 "min_days_before",
566 parseInt(e.target.value)
567 ),
568 min: "0"
569 }
570 ),
571 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: __(
572 "Apply only when the selected departure date is at least this many days away from today. Example: 30 means 30+ days before departure."
573 ) })
574 ] }),
575 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
576 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "max_days_before", children: __("Maximum Days Before Departure") }),
577 /* @__PURE__ */ jsxRuntimeExports.jsx(
578 Input,
579 {
580 id: "max_days_before",
581 type: "number",
582 value: formData.max_days_before,
583 onChange: (e) => handleChange(
584 "max_days_before",
585 parseInt(e.target.value)
586 ),
587 min: "0"
588 }
589 ),
590 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: __(
591 "Apply only when the selected departure date is within this many days from today. Example: 7 means 0–7 days before departure."
592 ) })
593 ] }),
594 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-3 bg-gray-50 dark:bg-gray-900/40 border border-gray-200 dark:border-gray-800 rounded-lg", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-700 dark:text-gray-300", children: __(
595 "If you set both Minimum and Maximum, the rule applies only inside that window (Minimum ≤ days before departure ≤ Maximum)."
596 ) }) }),
597 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
598 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "start_date", children: __("Start Date (Optional)") }),
599 /* @__PURE__ */ jsxRuntimeExports.jsx(
600 DatePicker,
601 {
602 value: formData.start_date,
603 onChange: (value) => handleChange("start_date", value),
604 placeholder: __("Select start date")
605 }
606 ),
607 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: __(
608 "Optional lifetime limit. If set, this rule will run only on or after this date. Leave empty for no restriction."
609 ) })
610 ] }),
611 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
612 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "end_date", children: __("End Date (Optional)") }),
613 /* @__PURE__ */ jsxRuntimeExports.jsx(
614 DatePicker,
615 {
616 value: formData.end_date,
617 onChange: (value) => handleChange("end_date", value),
618 placeholder: __("Select end date")
619 }
620 ),
621 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: __(
622 "Optional lifetime limit. If set, this rule will run only on or before this date. Leave empty for no restriction."
623 ) })
624 ] })
625 ] }),
626 formData.rule_type === "inventory" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
627 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
628 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "min_inventory", children: __("Low-inventory threshold (seats)") }),
629 /* @__PURE__ */ jsxRuntimeExports.jsx(
630 Input,
631 {
632 id: "min_inventory",
633 type: "number",
634 value: formData.min_inventory,
635 onChange: (e) => handleChange(
636 "min_inventory",
637 parseInt(e.target.value)
638 ),
639 min: "0"
640 }
641 ),
642 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: __(
643 "When seats remaining ≤ this number, the rule applies a markup (scarcity)."
644 ) })
645 ] }),
646 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
647 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "max_inventory", children: __("High-inventory threshold (seats)") }),
648 /* @__PURE__ */ jsxRuntimeExports.jsx(
649 Input,
650 {
651 id: "max_inventory",
652 type: "number",
653 value: formData.max_inventory,
654 onChange: (e) => handleChange(
655 "max_inventory",
656 parseInt(e.target.value)
657 ),
658 min: "0"
659 }
660 ),
661 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: __(
662 "When seats remaining ≥ this number, the rule applies a partial discount (50% of the magnitude)."
663 ) })
664 ] })
665 ] }),
666 formData.rule_type === "seasonal" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
667 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
668 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "start_date", children: __("Season Start (Departure Date)") }),
669 /* @__PURE__ */ jsxRuntimeExports.jsx(
670 DatePicker,
671 {
672 value: formData.start_date,
673 onChange: (value) => handleChange("start_date", value),
674 placeholder: __("Select start date")
675 }
676 ),
677 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: __(
678 "Earliest departure date this seasonal rule applies to."
679 ) })
680 ] }),
681 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
682 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "end_date", children: __("Season End (Departure Date)") }),
683 /* @__PURE__ */ jsxRuntimeExports.jsx(
684 DatePicker,
685 {
686 value: formData.end_date,
687 onChange: (value) => handleChange("end_date", value),
688 placeholder: __("Select end date")
689 }
690 ),
691 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: __(
692 "Latest departure date this seasonal rule applies to. The rule matches whenever the booking's departure date falls between these two dates (inclusive)."
693 ) })
694 ] }),
695 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-3 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-yellow-700 dark:text-yellow-300", children: __(
696 "💡 Tip: Use a positive Adjustment Value for peak-season markup (e.g. 25% during summer) or a negative value for an off-peak discount."
697 ) }) })
698 ] }),
699 formData.rule_type === "demand" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
700 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
701 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "demand_threshold_high", children: __("High Demand Threshold (%)") }),
702 /* @__PURE__ */ jsxRuntimeExports.jsx(
703 Input,
704 {
705 id: "demand_threshold_high",
706 type: "number",
707 value: formData.demand_threshold_high,
708 onChange: (e) => handleChange(
709 "demand_threshold_high",
710 parseInt(e.target.value)
711 ),
712 min: "0",
713 max: "100"
714 }
715 ),
716 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: __(
717 "Apply pricing when booking velocity exceeds this percentage (e.g., 80 = high demand)"
718 ) })
719 ] }),
720 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
721 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "demand_threshold_low", children: __("Low Demand Threshold (%)") }),
722 /* @__PURE__ */ jsxRuntimeExports.jsx(
723 Input,
724 {
725 id: "demand_threshold_low",
726 type: "number",
727 value: formData.demand_threshold_low,
728 onChange: (e) => handleChange(
729 "demand_threshold_low",
730 parseInt(e.target.value)
731 ),
732 min: "0",
733 max: "100"
734 }
735 ),
736 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: __(
737 "Apply pricing when booking velocity falls below this percentage (e.g., 30 = low demand)"
738 ) })
739 ] }),
740 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-blue-700 dark:text-blue-300", children: __(
741 "💡 Demand scores are calculated automatically via cron job based on booking velocity."
742 ) }) })
743 ] }),
744 formData.rule_type === "time_based" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
745 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
746 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: __("Apply Pricing On") }),
747 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-2 gap-2 mt-2", children: [
748 "monday",
749 "tuesday",
750 "wednesday",
751 "thursday",
752 "friday",
753 "saturday",
754 "sunday"
755 ].map((day) => {
756 var _a;
757 return /* @__PURE__ */ jsxRuntimeExports.jsxs(
758 "div",
759 {
760 className: "flex items-center gap-2",
761 children: [
762 /* @__PURE__ */ jsxRuntimeExports.jsx(
763 "input",
764 {
765 type: "checkbox",
766 id: `day_${day}`,
767 checked: (_a = formData.weekend_days) == null ? void 0 : _a.includes(day),
768 onChange: (e) => {
769 const current = formData.weekend_days || [];
770 if (e.target.checked) {
771 handleChange("weekend_days", [
772 ...current,
773 day
774 ]);
775 } else {
776 handleChange(
777 "weekend_days",
778 current.filter((d) => d !== day)
779 );
780 }
781 },
782 className: "w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
783 }
784 ),
785 /* @__PURE__ */ jsxRuntimeExports.jsx(
786 Label,
787 {
788 htmlFor: `day_${day}`,
789 className: "mb-0 capitalize",
790 children: __(day)
791 }
792 )
793 ]
794 },
795 day
796 );
797 }) }),
798 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-2", children: __(
799 "Selected days are matched against the booking's DEPARTURE date — e.g. tick Saturday + Sunday to apply a weekend premium to Saturday/Sunday departures, regardless of when the customer books."
800 ) })
801 ] }),
802 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
803 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "start_date", children: __("Start Date (Optional)") }),
804 /* @__PURE__ */ jsxRuntimeExports.jsx(
805 DatePicker,
806 {
807 value: formData.start_date,
808 onChange: (value) => handleChange("start_date", value),
809 placeholder: __("Select start date")
810 }
811 ),
812 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: __(
813 "Limits the rule to departures on or after this date. Leave empty for no restriction."
814 ) })
815 ] }),
816 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
817 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "end_date", children: __("End Date (Optional)") }),
818 /* @__PURE__ */ jsxRuntimeExports.jsx(
819 DatePicker,
820 {
821 value: formData.end_date,
822 onChange: (value) => handleChange("end_date", value),
823 placeholder: __("Select end date")
824 }
825 ),
826 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: __(
827 "Limits the rule to departures on or before this date. Leave empty for no restriction."
828 ) })
829 ] }),
830 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-3 bg-indigo-50 dark:bg-indigo-900/20 border border-indigo-200 dark:border-indigo-800 rounded-lg", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-indigo-700 dark:text-indigo-300", children: __(
831 "💡 Sign matters: a positive Adjustment Value adds a premium on the selected days; a negative value gives a discount."
832 ) }) })
833 ] })
834 ] })
835 ] })
836 ] })
837 ] }),
838 formData.rule_type && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
839 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
840 /* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, { className: "pb-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-base", children: __("Status") }) }),
841 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-3", children: [
842 /* @__PURE__ */ jsxRuntimeExports.jsxs(
843 Select,
844 {
845 value: formData.status,
846 onChange: (e) => handleChange("status", e.target.value),
847 children: [
848 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "active", children: __("Active") }),
849 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "inactive", children: __("Inactive") })
850 ]
851 }
852 ),
853 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: __("Only active rules will be applied to trips.") })
854 ] })
855 ] }),
856 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
857 /* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, { className: "pb-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-base", children: __("Priority") }) }),
858 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-3", children: [
859 /* @__PURE__ */ jsxRuntimeExports.jsx(
860 Input,
861 {
862 type: "number",
863 value: formData.priority,
864 onChange: (e) => handleChange("priority", parseInt(e.target.value)),
865 min: "1",
866 max: "100"
867 }
868 ),
869 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: __(
870 "A higher number = higher priority (1–100). When more than one rule matches, the global Rule Priority Mode in Settings decides whether the highest-priority rule wins, every rule stacks, or the customer gets the best price."
871 ) })
872 ] })
873 ] }),
874 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
875 /* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, { className: "pb-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-base", children: __("Applicable To") }) }),
876 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-3", children: [
877 /* @__PURE__ */ jsxRuntimeExports.jsxs(
878 Select,
879 {
880 value: formData.applicable_trips,
881 onChange: (e) => handleChange("applicable_trips", e.target.value),
882 children: [
883 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "all", children: __("All Trips") }),
884 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "specific", children: __("Specific Trips") })
885 ]
886 }
887 ),
888 formData.applicable_trips === "specific" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 space-y-2 relative", children: [
889 /* @__PURE__ */ jsxRuntimeExports.jsx(
890 "div",
891 {
892 className: "border border-gray-300 dark:border-gray-600 rounded-lg p-2 cursor-pointer hover:border-gray-400 dark:hover:border-gray-500 bg-white dark:bg-gray-800",
893 onClick: () => setShowTripDropdown(!showTripDropdown),
894 children: formData.trip_ids.length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap gap-1.5", children: [
895 formData.trip_ids.slice(0, 2).map((tripId) => {
896 const trip = tripOptions.find(
897 (t) => t.value === tripId
898 );
899 return /* @__PURE__ */ jsxRuntimeExports.jsxs(
900 "span",
901 {
902 className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-300 rounded",
903 children: [
904 (trip == null ? void 0 : trip.label) || `Trip #${tripId}`,
905 " ",
906 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-600 dark:text-blue-400", children: [
907 "#",
908 tripId
909 ] }),
910 /* @__PURE__ */ jsxRuntimeExports.jsx(
911 "button",
912 {
913 type: "button",
914 onClick: (e) => {
915 e.stopPropagation();
916 handleChange(
917 "trip_ids",
918 formData.trip_ids.filter(
919 (id) => id !== tripId
920 )
921 );
922 },
923 className: "hover:text-blue-600 dark:hover:text-blue-200",
924 children: "×"
925 }
926 )
927 ]
928 },
929 tripId
930 );
931 }),
932 formData.trip_ids.length > 2 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center px-2 py-0.5 text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 rounded", children: [
933 "+",
934 formData.trip_ids.length - 2,
935 " ",
936 __("more")
937 ] })
938 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: __("Click to select trips...") })
939 }
940 ),
941 showTripDropdown && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute z-50 w-full mt-1 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg shadow-lg max-h-64 overflow-hidden", children: [
942 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-2 border-b border-gray-200 dark:border-gray-700", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
943 Input,
944 {
945 type: "text",
946 placeholder: __("Search trips..."),
947 value: tripSearchQuery,
948 onChange: (e) => setTripSearchQuery(e.target.value),
949 className: "w-full text-sm",
950 onClick: (e) => e.stopPropagation()
951 }
952 ) }),
953 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "max-h-48 overflow-y-auto", children: tripOptions.map((trip) => {
954 const isSelected = formData.trip_ids.includes(
955 trip.value
956 );
957 return /* @__PURE__ */ jsxRuntimeExports.jsxs(
958 "div",
959 {
960 className: "px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer flex items-center gap-2",
961 onClick: (e) => {
962 e.stopPropagation();
963 if (isSelected) {
964 handleChange(
965 "trip_ids",
966 formData.trip_ids.filter(
967 (id) => id !== trip.value
968 )
969 );
970 } else {
971 handleChange("trip_ids", [
972 ...formData.trip_ids,
973 trip.value
974 ]);
975 }
976 },
977 children: [
978 /* @__PURE__ */ jsxRuntimeExports.jsx(
979 "div",
980 {
981 className: `w-4 h-4 border-2 rounded flex items-center justify-center ${isSelected ? "bg-blue-600 border-blue-600" : "border-gray-300 dark:border-gray-600"}`,
982 children: isSelected && /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "w-3 h-3 text-white" })
983 }
984 ),
985 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 text-sm text-gray-900 dark:text-white", children: trip.label }),
986 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: [
987 "#",
988 trip.value
989 ] })
990 ]
991 },
992 trip.value
993 );
994 }) })
995 ] })
996 ] })
997 ] })
998 ] }),
999 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
1000 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
1001 /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-base", children: __("When will this rule apply?") }),
1002 /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: __(
1003 "Run a simulation with the current settings to see which scenarios trigger the discount/markup. Nothing is saved."
1004 ) })
1005 ] }),
1006 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { children: [
1007 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-end gap-3 mb-3", children: [
1008 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
1009 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "preview-base-price", children: __("Sample base price") }),
1010 /* @__PURE__ */ jsxRuntimeExports.jsx(
1011 Input,
1012 {
1013 id: "preview-base-price",
1014 type: "number",
1015 min: 1,
1016 step: "0.01",
1017 value: previewBasePrice,
1018 onChange: (e) => setPreviewBasePrice(Number(e.target.value) || 0)
1019 }
1020 )
1021 ] }),
1022 /* @__PURE__ */ jsxRuntimeExports.jsx(
1023 Button,
1024 {
1025 type: "button",
1026 onClick: handleRunSimulation,
1027 disabled: previewLoading || !formData.rule_type,
1028 children: previewLoading ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1029 /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "w-4 h-4 mr-2 animate-spin" }),
1030 __("Running...")
1031 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1032 /* @__PURE__ */ jsxRuntimeExports.jsx(Target, { className: "w-4 h-4 mr-2" }),
1033 __("Run Preview")
1034 ] })
1035 }
1036 )
1037 ] }),
1038 previewError && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-red-600 dark:text-red-400 mb-2", children: previewError }),
1039 previewResult && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-x-auto rounded-md border border-gray-200 dark:border-gray-700", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full text-sm", children: [
1040 /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { className: "bg-gray-50 dark:bg-gray-900", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "text-left", children: [
1041 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-3 py-2 font-medium", children: __("Scenario") }),
1042 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-3 py-2 font-medium", children: __("Final Price") }),
1043 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-3 py-2 font-medium", children: __("Change") }),
1044 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-3 py-2 font-medium", children: __("Rule") })
1045 ] }) }),
1046 /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { children: previewResult.scenarios.map((s, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
1047 "tr",
1048 {
1049 className: "border-t border-gray-200 dark:border-gray-700",
1050 children: [
1051 /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "px-3 py-2", children: [
1052 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: s.label }),
1053 s.departure_date && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: s.departure_date })
1054 ] }),
1055 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-2 font-medium", children: previewResult.base_price > 0 ? `$${s.final_price.toFixed(2)}` : "—" }),
1056 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-2", children: s.applies ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
1057 "span",
1058 {
1059 className: s.adjustment < 0 ? "text-green-700 dark:text-green-400" : "text-orange-700 dark:text-orange-400",
1060 children: [
1061 s.adjustment > 0 ? "+" : "",
1062 "$",
1063 s.adjustment.toFixed(2),
1064 " (",
1065 s.adjustment_percent > 0 ? "+" : "",
1066 s.adjustment_percent.toFixed(2),
1067 "%)"
1068 ]
1069 }
1070 ) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-gray-500 dark:text-gray-400", children: "—" }) }),
1071 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-2", children: s.applies ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1 text-green-700 dark:text-green-400", children: [
1072 /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "w-3.5 h-3.5" }),
1073 __("Fires")
1074 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-gray-500 dark:text-gray-400", children: __("Skipped") }) })
1075 ]
1076 },
1077 i
1078 )) })
1079 ] }) })
1080 ] })
1081 ] }),
1082 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3", children: [
1083 /* @__PURE__ */ jsxRuntimeExports.jsx(
1084 Button,
1085 {
1086 type: "submit",
1087 disabled: saveMutation.isPending,
1088 className: "w-full",
1089 children: saveMutation.isPending ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1090 /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "w-4 h-4 mr-2 animate-spin" }),
1091 __("Saving...")
1092 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1093 /* @__PURE__ */ jsxRuntimeExports.jsx(Save, { className: "w-4 h-4 mr-2" }),
1094 isEdit ? __("Update Rule") : __("Create Rule")
1095 ] })
1096 }
1097 ),
1098 /* @__PURE__ */ jsxRuntimeExports.jsxs(
1099 Button,
1100 {
1101 type: "button",
1102 variant: "outline",
1103 onClick: handleCancel,
1104 className: "w-full",
1105 children: [
1106 /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowLeft, { className: "w-4 h-4 mr-2" }),
1107 __("Cancel")
1108 ]
1109 }
1110 )
1111 ] })
1112 ] })
1113 ] }),
1114 false
1115 ] }),
1116 /* @__PURE__ */ jsxRuntimeExports.jsx(
1117 RuleTypeSelectionModal,
1118 {
1119 isOpen: showRuleTypeModal,
1120 onClose: () => setShowRuleTypeModal(false),
1121 onSelectType: handleSelectRuleType
1122 }
1123 )
1124 ] });
1125 };
1126 export {
1127 DynamicPricingRuleForm as default
1128 };
1129 //# sourceMappingURL=DynamicPricingRuleForm-C99aT0Zu.js.map
1130