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/components/ui/calendar.tsx +85 -7 3.0.6 → 3.0.17 View file →
@@ -10,8 +10,12 @@
10 10 isSameMonth,
11 11 isSameDay,
12 12 addMonths,
13 13 subMonths,
14 + setMonth,
15 + setYear,
16 + getMonth,
17 + getYear,
14 18 isToday,
15 19 } from "date-fns";
16 20
17 21 export interface CalendarProps {
@@ -22,8 +26,23 @@
22 26 maxDate?: Date;
23 27 className?: string;
24 28 }
25 29
30 +const MONTH_LABELS = [
31 + "January",
32 + "February",
33 + "March",
34 + "April",
35 + "May",
36 + "June",
37 + "July",
38 + "August",
39 + "September",
40 + "October",
41 + "November",
42 + "December",
43 +];
44 +
26 45 export const Calendar: React.FC<CalendarProps> = ({
27 46 selected,
28 47 onSelect,
29 48 disabled,
@@ -30,10 +49,39 @@
30 49 minDate,
31 50 maxDate,
32 51 className = "",
33 52 }) => {
34 - const [currentMonth, setCurrentMonth] = React.useState(new Date());
53 + // Open on the selected date (e.g. a saved date of birth lands on its own
54 + // year), falling back to today for a blank field.
55 + const [currentMonth, setCurrentMonth] = React.useState<Date>(
56 + () => selected ?? new Date(),
57 + );
35 58
59 + // When the bound value changes (popover reopened with an existing value),
60 + // jump the grid to that month so the user isn't stranded on today.
61 + React.useEffect(() => {
62 + if (selected) setCurrentMonth(selected);
63 + }, [selected]);
64 +
65 + // Year range for the dropdown: honour min/max when provided, else a wide
66 + // span that comfortably covers dates of birth (back ~120 years) and future
67 + // trip dates (~10 years ahead). Listed newest-first.
68 + const today = new Date();
69 + const fromYear = minDate ? getYear(minDate) : getYear(today) - 120;
70 + const toYear = maxDate ? getYear(maxDate) : getYear(today) + 10;
71 + const years: number[] = [];
72 + for (let y = toYear; y >= fromYear; y--) {
73 + years.push(y);
74 + }
75 +
76 + const handleMonthSelect = (monthIndex: number) => {
77 + setCurrentMonth(setMonth(currentMonth, monthIndex));
78 + };
79 +
80 + const handleYearSelect = (year: number) => {
81 + setCurrentMonth(setYear(currentMonth, year));
82 + };
83 +
36 84 const monthStart = startOfMonth(currentMonth);
37 85 const monthEnd = endOfMonth(currentMonth);
38 86 const calendarStart = startOfWeek(monthStart, { weekStartsOn: 0 });
39 87 const calendarEnd = endOfWeek(monthEnd, { weekStartsOn: 0 });
@@ -62,23 +110,53 @@
62 110 const weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
63 111
64 112 return (
65 113 <div className={`p-3 ${className}`}>
66 - <div className="flex items-center justify-between mb-4">
114 + <div className="flex items-center justify-between gap-1 mb-4">
67 115 <button
68 116 type="button"
69 117 onClick={previousMonth}
70 - className="p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
118 + aria-label="Previous month"
119 + className="p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors shrink-0"
71 120 >
72 121 <ChevronLeft className="w-4 h-4" />
73 122 </button>
74 - <h3 className="text-sm font-semibold text-gray-900 dark:text-white">
75 - {format(currentMonth, "MMMM yyyy")}
76 - </h3>
123 +
124 + <div className="flex items-center gap-1">
125 + {/* Month dropdown — jump to any month in one click */}
126 + <select
127 + value={getMonth(currentMonth)}
128 + onChange={(e) => handleMonthSelect(Number(e.target.value))}
129 + aria-label="Month"
130 + className="text-sm font-semibold text-gray-900 dark:text-white bg-transparent border border-transparent hover:border-gray-300 dark:hover:border-gray-600 focus:border-blue-500 rounded-md px-1 py-0.5 cursor-pointer focus:outline-none dark:bg-gray-800"
131 + >
132 + {MONTH_LABELS.map((label, index) => (
133 + <option key={label} value={index}>
134 + {label}
135 + </option>
136 + ))}
137 + </select>
138 +
139 + {/* Year dropdown — jump straight to e.g. 1990 without month-stepping */}
140 + <select
141 + value={getYear(currentMonth)}
142 + onChange={(e) => handleYearSelect(Number(e.target.value))}
143 + aria-label="Year"
144 + className="text-sm font-semibold text-gray-900 dark:text-white bg-transparent border border-transparent hover:border-gray-300 dark:hover:border-gray-600 focus:border-blue-500 rounded-md px-1 py-0.5 cursor-pointer focus:outline-none dark:bg-gray-800"
145 + >
146 + {years.map((year) => (
147 + <option key={year} value={year}>
148 + {year}
149 + </option>
150 + ))}
151 + </select>
152 + </div>
153 +
77 154 <button
78 155 type="button"
79 156 onClick={nextMonth}
80 - className="p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
157 + aria-label="Next month"
158 + className="p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors shrink-0"
81 159 >
82 160 <ChevronRight className="w-4 h-4" />
83 161 </button>
84 162 </div>