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/Availability.tsx +55 -1 3.0.13 → 3.0.16 View file →
@@ -348,8 +348,23 @@
348 348 },
349 349 enabled: !!selectedTripId,
350 350 });
351 351
352 + // Dates generated by the trip's recurring rules — shown READ-ONLY on the
353 + // calendar so a rules-only trip isn't a blank screen. Kept separate from the
354 + // main (specific-date) query so the paginated List view and its edit/delete/
355 + // bulk actions are untouched: those only ever operate on real rows.
356 + const { data: generatedData } = useQuery({
357 + queryKey: ["availability-generated", selectedTripId],
358 + queryFn: async () => {
359 + if (!selectedTripId) return { dates: [] };
360 + return await apiClient.get("/availability/generated", {
361 + params: { trip_id: selectedTripId },
362 + });
363 + },
364 + enabled: !!selectedTripId && viewMode === "calendar",
365 + });
366 +
352 367 // Delete availability date
353 368 const deleteMutation = useMutation({
354 369 mutationFn: async (id: string) => {
355 370 return await apiClient.delete(`/availability/${id}`);
@@ -540,8 +555,42 @@
540 555
541 556 return filtered;
542 557 }, [availabilityData, searchTerm, statusFilter, monthFilter, selectedTrip]);
543 558
559 + // Calendar-only dataset: the specific dates above PLUS read-only rule-generated
560 + // dates for any (date, time) that has no specific row. A specific row always
561 + // wins — the operator can edit it — so an existing manual override is never
562 + // masked by its rule. Only the calendar uses this; the List view keeps using
563 + // filteredDates untouched.
564 + const calendarDates = useMemo(() => {
565 + const rows = [...filteredDates];
566 + // Dedup against ALL specific rows, not just the filtered ones: a specific
567 + // override must mask its rule-generated twin even when the active status/
568 + // month filter has hidden that override — otherwise the calendar would show
569 + // the rule's "available" date over a real blocked/edited row.
570 + const seen = new Set(
571 + (availabilityData?.dates || []).map(
572 + (d: any) => `${d.departure_date}|${d.departure_time || ""}`,
573 + ),
574 + );
575 + const generated = (generatedData as any)?.dates || [];
576 + for (const g of generated) {
577 + const key = `${g.departure_date}|${g.departure_time || ""}`;
578 + if (seen.has(key)) continue;
579 + seen.add(key);
580 + rows.push({
581 + ...g,
582 + id: String(g.id),
583 + seats_remaining:
584 + (g.available_seats || 0) > 10
585 + ? "10+"
586 + : String(g.available_seats || 0),
587 + is_virtual: true,
588 + } as unknown as AvailabilityDate);
589 + }
590 + return rows;
591 + }, [filteredDates, generatedData, availabilityData]);
592 +
544 593 // Get available months for filter (only for multi-day trips)
545 594 const availableMonths = useMemo(() => {
546 595 if (!availabilityData?.dates || selectedTrip?.trip_type === "single_day")
547 596 return [];
@@ -1782,12 +1831,17 @@
1782 1831 </div>
1783 1832 ) : (
1784 1833 <div className="mt-4">
1785 1834 <AvailabilityCalendar
1786 - dates={filteredDates}
1835 + dates={calendarDates}
1787 1836 tripType={selectedTrip?.trip_type}
1788 1837 currency={selectedTrip?.currency || "USD"}
1789 1838 onDateClick={(date) => {
1839 + // Rule-generated dates are read-only — they're edited
1840 + // on the Recurring Rules tab, not as individual rows.
1841 + if ((date as any).is_virtual) {
1842 + return;
1843 + }
1790 1844 navigate({
1791 1845 subpage: "trips",
1792 1846 tab: "availability",
1793 1847 action: "edit",