PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.17
Yatra – Travel Booking & Tour Operator Software v3.0.17
3.0.17 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 All 85 releases
← All changes | resources/js/pages/TripForm.tsx +70 -3 3.0.13 → 3.0.17 View file →
@@ -300,8 +300,9 @@
300 300 activities: namesFromIds(formData?.activities),
301 301 difficulty_level: formData?.difficulty_level ?? "",
302 302 duration_days: formData?.duration_days ?? "",
303 303 duration_nights: formData?.duration_nights ?? "",
304 + duration_hours: formData?.duration_hours ?? "",
304 305 best_season: formData?.best_season ?? "",
305 306 price: formData?.price ?? "",
306 307 deposit_percentage: formData?.deposit_percentage ?? "",
307 308 booking_deadline_hours: formData?.booking_deadline_hours ?? "",
@@ -366,8 +367,9 @@
366 367 // Duration & Schedule
367 368 trip_type: "single_day" | "multi_day" | "flexible";
368 369 duration_days: string;
369 370 duration_nights: string;
371 + duration_hours: string;
370 372 available_from: string;
371 373 available_to: string;
372 374 booking_window_days: string;
373 375 seasonal_availability: string;
@@ -715,8 +717,9 @@
715 717 ],
716 718 trip_type: "multi_day",
717 719 duration_days: "7",
718 720 duration_nights: "6",
721 + duration_hours: "",
719 722 available_from: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
720 723 .toISOString()
721 724 .split("T")[0], // 30 days from now
722 725 available_to: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000)
@@ -972,8 +975,9 @@
972 975 ],
973 976 trip_type: "multi_day",
974 977 duration_days: "14",
975 978 duration_nights: "13",
979 + duration_hours: "",
976 980 available_from: new Date(Date.now() + 60 * 24 * 60 * 60 * 1000)
977 981 .toISOString()
978 982 .split("T")[0],
979 983 available_to: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000)
@@ -1234,8 +1238,9 @@
1234 1238 ],
1235 1239 trip_type: "multi_day",
1236 1240 duration_days: "10",
1237 1241 duration_nights: "9",
1242 + duration_hours: "",
1238 1243 available_from: new Date(Date.now() + 45 * 24 * 60 * 60 * 1000)
1239 1244 .toISOString()
1240 1245 .split("T")[0],
1241 1246 available_to: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000)
@@ -1473,8 +1478,9 @@
1473 1478 landmarks: [],
1474 1479 trip_type: "multi_day",
1475 1480 duration_days: "",
1476 1481 duration_nights: "",
1482 + duration_hours: "",
1477 1483 available_from: "",
1478 1484 available_to: "",
1479 1485 booking_window_days: "",
1480 1486 seasonal_availability: "",
@@ -2216,8 +2222,12 @@
2216 2222 ? "single_day"
2217 2223 : "multi_day")) as "single_day" | "multi_day" | "flexible",
2218 2224 duration_days: tripData.duration_days?.toString() || "",
2219 2225 duration_nights: tripData.duration_nights?.toString() || "",
2226 + // 0 / null both mean "not hour-based" — show an empty field, not "0".
2227 + duration_hours: tripData.duration_hours
2228 + ? String(tripData.duration_hours)
2229 + : "",
2220 2230 available_from: tripData.available_from || "",
2221 2231 available_to: tripData.available_to || "",
2222 2232 booking_window_days: tripData.booking_window_days?.toString() || "",
2223 2233 seasonal_availability: tripData.seasonal_availability || "",
@@ -2479,8 +2489,9 @@
2479 2489 "featured_image",
2480 2490 "trip_type",
2481 2491 "duration_days",
2482 2492 "duration_nights",
2493 + "duration_hours",
2483 2494 ],
2484 2495 location: ["destinations", "starting_location", "ending_location"],
2485 2496 duration: ["available_from", "available_to", "booking_window_days"],
2486 2497 pricing: ["original_price", "discounted_price", "price_types"],
@@ -3315,8 +3326,20 @@
3315 3326 duration_days: data.duration_days ? parseInt(data.duration_days) : null,
3316 3327 duration_nights: data.duration_nights
3317 3328 ? parseInt(data.duration_nights)
3318 3329 : null,
3330 + // Hour-based day tours. This key was missing from the payload, so the
3331 + // Duration (Hours) field could never be saved: the form kept the value,
3332 + // the request dropped it, and the field reloaded empty. Send 0 (never
3333 + // null) when the field is blank — the validator treats null as "not
3334 + // provided" and would leave a stale hours value on a trip switched
3335 + // back to multi-day, which the storefront would then show as hours.
3336 + duration_hours:
3337 + data.duration_hours !== "" &&
3338 + data.duration_hours != null &&
3339 + !isNaN(parseInt(data.duration_hours))
3340 + ? parseInt(data.duration_hours)
3341 + : 0,
3319 3342 available_from: data.available_from || null,
3320 3343 available_to: data.available_to || null,
3321 3344 booking_window_days: data.booking_window_days
3322 3345 ? parseInt(data.booking_window_days)
@@ -4552,11 +4575,17 @@
4552 4575 type="radio"
4553 4576 name="trip_type"
4554 4577 value="multi_day"
4555 4578 checked={formData.trip_type === "multi_day"}
4556 - onChange={(e) =>
4557 - handleFieldChange("trip_type", e.target.value)
4558 - }
4579 + onChange={(e) => {
4580 + handleFieldChange("trip_type", e.target.value);
4581 + // A multi-day trip is never hour-based — drop any
4582 + // hours set while it was a single-day tour.
4583 + setFormData((prev) => ({
4584 + ...prev,
4585 + duration_hours: "",
4586 + }));
4587 + }}
4559 4588 className="sr-only"
4560 4589 />
4561 4590 <div className="flex flex-1">
4562 4591 <div className="flex flex-col">
@@ -4699,8 +4728,46 @@
4699 4728 </p>
4700 4729 )}
4701 4730 </div>
4702 4731 </div>
4732 +
4733 + {/* Duration (Hours) — hour-based single-day tours */}
4734 + {formData.trip_type === "single_day" && (
4735 + <div className="mt-4">
4736 + <label
4737 + htmlFor="duration_hours"
4738 + className="block text-xs font-normal text-gray-500 dark:text-gray-400 mb-1.5"
4739 + >
4740 + {__("Duration (Hours)", "yatra")}
4741 + </label>
4742 + <Input
4743 + id="duration_hours"
4744 + type="number"
4745 + min="1"
4746 + max="24"
4747 + value={formData.duration_hours}
4748 + onChange={(e) =>
4749 + handleFieldChange("duration_hours", e.target.value)
4750 + }
4751 + placeholder={__("e.g., 8", "yatra")}
4752 + className={
4753 + errors.duration_hours ? "border-red-500" : ""
4754 + }
4755 + />
4756 + <p className="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
4757 + {__(
4758 + "Optional — for an hour-based day tour (e.g. an 8-hour experience). When set, the tour's duration shows in hours and Google Calendar creates a same-day timed event instead of an all-day one. Leave empty for a normal day tour.",
4759 + "yatra",
4760 + )}
4761 + </p>
4762 + {errors.duration_hours && (
4763 + <p className="mt-1.5 text-sm text-red-600 dark:text-red-400 flex items-center gap-1">
4764 + <AlertCircle className="w-4 h-4" />
4765 + {errors.duration_hours}
4766 + </p>
4767 + )}
4768 + </div>
4769 + )}
4703 4770 </div>
4704 4771 </div>
4705 4772 </div>
4706 4773 </div>