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
← All changes | resources/js/pages/AvailabilityForm.tsx +58 -18 3.0.13 → 3.0.16 View file →
@@ -14,9 +14,9 @@
14 14 Loader2,
15 15 MapPin,
16 16 CheckCircle2,
17 17 } from "lucide-react";
18 -import { __ } from "../lib/i18n";
18 +import { __, sprintf } from "../lib/i18n";
19 19 import { toDateValue } from "../lib/dateFormat";
20 20 import { Button } from "../components/ui/button";
21 21 import { Input } from "../components/ui/input";
22 22 import { Select } from "../components/ui/select";
@@ -147,8 +147,27 @@
147 147 },
148 148 enabled: !!tripId,
149 149 });
150 150
151 + const isSingleDayTrip = tripData?.trip_type === "single_day";
152 +
153 + // A day tour can run several departures a day ("multiple time slots", set on
154 + // the trip). Only then does a date need a departure time — it identifies
155 + // which slot this row overrides, exactly as the server resolves it
156 + // (AvailabilityResolutionService: has_default_time_slots && single_day).
157 + // A day tour with one departure stores no time, which every consumer
158 + // already handles (CapacityService falls back to the rule/date seats).
159 + const hasTimeSlots = isSingleDayTrip && !!tripData?.has_default_time_slots;
160 +
161 + // The trip's max travellers seeds a new date's capacity so it doesn't have
162 + // to be retyped for every date. It stays editable per date, and it is still
163 + // stored explicitly: an empty/zero seat count means "sold out" to the
164 + // availability layer, so it must never be left blank.
165 + const tripMaxTravelers = (() => {
166 + const raw = Number(tripData?.max_travelers ?? 0);
167 + return Number.isFinite(raw) && raw > 0 ? raw : 0;
168 + })();
169 +
151 170 // Fetch traveler categories
152 171 const { data: categoriesData, isLoading: isLoadingCategories } = useQuery({
153 172 queryKey: ["traveler-categories"],
154 173 queryFn: async () => {
@@ -199,8 +218,13 @@
199 218 : tripData.pricing_type || "regular";
200 219
201 220 setFormData((prev) => ({
202 221 ...prev,
222 + // Only seeds an untouched field — never overwrites a typed value if
223 + // the trip query refetches.
224 + total_seats:
225 + prev.total_seats ||
226 + (tripMaxTravelers > 0 ? String(tripMaxTravelers) : prev.total_seats),
203 227 from_location: tripData.starting_location || "",
204 228 to_location: tripData.ending_location || "",
205 229 from_latitude: coordFromApi(tripData.starting_latitude),
206 230 from_longitude: coordFromApi(tripData.starting_longitude),
@@ -209,9 +233,9 @@
209 233 // Default pricing type based on trip's pricing type
210 234 pricing_type: effectivePricingType as "regular" | "traveler_based",
211 235 }));
212 236 }
213 - }, [tripData, isEditMode]);
237 + }, [tripData, isEditMode, tripMaxTravelers]);
214 238
215 239 useEffect(() => {
216 240 if (availabilityData) {
217 241 const totalSeats =
@@ -358,9 +382,9 @@
358 382 newErrors.arrival_date = __("Arrival date is required", "yatra");
359 383 }
360 384
361 385 // Validate dates and times based on trip type
362 - const isSingleDay = tripData?.trip_type === "single_day";
386 + const isSingleDay = isSingleDayTrip;
363 387
364 388 if (isSingleDay) {
365 389 // For single day trips, both dates should be the same
366 390 if (
@@ -372,21 +396,18 @@
372 396 "For single day trips, departure and arrival must be on the same date",
373 397 "yatra",
374 398 );
375 399 }
376 - // Validate times
377 - if (!formData.departure_time) {
400 + // Times are only needed when the day tour runs several departures a
401 + // day: the departure time is what identifies the slot. A single-
402 + // departure day tour saves without times (arrival time is display-only
403 + // and never required).
404 + if (hasTimeSlots && !formData.departure_time) {
378 405 newErrors.departure_time = __(
379 - "Departure time is required for single day trips",
406 + "Departure time is required when the trip has multiple time slots",
380 407 "yatra",
381 408 );
382 409 }
383 - if (!formData.arrival_time) {
384 - newErrors.arrival_time = __(
385 - "Arrival time is required for single day trips",
386 - "yatra",
387 - );
388 - }
389 410 // Validate that arrival time is after departure time
390 411 if (
391 412 formData.departure_time &&
392 413 formData.arrival_time &&
@@ -706,9 +727,15 @@
706 727 <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
707 728 <div>
708 729 <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5">
709 730 {__("Departure Time", "yatra")}{" "}
710 - <span className="text-red-500">*</span>
731 + {hasTimeSlots ? (
732 + <span className="text-red-500">*</span>
733 + ) : (
734 + <span className="text-gray-400 font-normal">
735 + {__("(optional)", "yatra")}
736 + </span>
737 + )}
711 738 </label>
712 739 <TimePicker
713 740 value={formData.departure_time}
714 741 onChange={(value: string) =>
@@ -725,9 +752,11 @@
725 752 </div>
726 753 <div>
727 754 <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5">
728 755 {__("Arrival Time", "yatra")}{" "}
729 - <span className="text-red-500">*</span>
756 + <span className="text-gray-400 font-normal">
757 + {__("(optional)", "yatra")}
758 + </span>
730 759 </label>
731 760 <TimePicker
732 761 value={formData.arrival_time}
733 762 onChange={(value: string) =>
@@ -1525,12 +1554,23 @@
1525 1554 {errors.total_seats}
1526 1555 </p>
1527 1556 )}
1528 1557 <HelpText
1529 - text={__(
1530 - "Maximum number of seats available for this date",
1531 - "yatra",
1532 - )}
1558 + text={
1559 + tripMaxTravelers > 0
1560 + ? sprintf(
1561 + /* translators: %d: the trip's max travellers. */
1562 + __(
1563 + "Seats for this date. Pre-filled from the trip's max travellers (%d) — change it for this date if it differs.",
1564 + "yatra",
1565 + ),
1566 + tripMaxTravelers,
1567 + )
1568 + : __(
1569 + "Maximum number of seats available for this date",
1570 + "yatra",
1571 + )
1572 + }
1533 1573 className="mt-1"
1534 1574 />
1535 1575 </div>
1536 1576 {isEditMode && (