PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.3
Yatra – Travel Booking & Tour Operator Software v3.0.3
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 2.0.10 All 83 releases
yatra / resources / js / components / ui / time-picker.tsx

time-picker.tsx in Yatra – Travel Booking & Tour Operator Software 3.0.3, at resources/js/components/ui/time-picker.tsx

200 lines 6.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import * as React from "react";
2 import { Clock } from "lucide-react";
3 import { Popover, PopoverContent, PopoverTrigger } from "./popover";
4 import { Button } from "./button";
5
6 export interface TimePickerProps {
7 value?: string; // HH:mm format
8 onChange?: (value: string) => void;
9 placeholder?: string;
10 disabled?: boolean;
11 className?: string;
12 error?: boolean;
13 }
14
15 export const TimePicker: React.FC<TimePickerProps> = ({
16 value,
17 onChange,
18 placeholder = "Select time",
19 disabled = false,
20 className = "",
21 error = false,
22 }) => {
23 const [open, setOpen] = React.useState(false);
24 const [hours, setHours] = React.useState<string>("");
25 const [minutes, setMinutes] = React.useState<string>("");
26 const [ampm, setAmpm] = React.useState<"AM" | "PM">("AM");
27
28 React.useEffect(() => {
29 if (value) {
30 const [h, m] = value.split(":");
31 const hour = parseInt(h);
32 if (hour === 0) {
33 setHours("12");
34 setAmpm("AM");
35 } else if (hour < 12) {
36 setHours(hour.toString());
37 setAmpm("AM");
38 } else if (hour === 12) {
39 setHours("12");
40 setAmpm("PM");
41 } else {
42 setHours((hour - 12).toString());
43 setAmpm("PM");
44 }
45 setMinutes(m || "00");
46 }
47 }, [value]);
48
49 const handleTimeSelect = (h: string, m: string, a: "AM" | "PM") => {
50 let hour24 = parseInt(h);
51 if (a === "PM" && hour24 !== 12) {
52 hour24 += 12;
53 } else if (a === "AM" && hour24 === 12) {
54 hour24 = 0;
55 }
56 const timeString = `${hour24.toString().padStart(2, "0")}:${m.padStart(2, "0")}`;
57 onChange?.(timeString);
58 setOpen(false);
59 };
60
61 const formatDisplayTime = () => {
62 if (!value) return "";
63 const [h, m] = value.split(":");
64 const hour = parseInt(h);
65 if (hour === 0) return `12:${m} AM`;
66 if (hour < 12) return `${hour}:${m} AM`;
67 if (hour === 12) return `12:${m} PM`;
68 return `${hour - 12}:${m} PM`;
69 };
70
71 const hourOptions = Array.from({ length: 12 }, (_, i) => (i + 1).toString());
72 const minuteOptions = Array.from({ length: 60 }, (_, i) =>
73 i.toString().padStart(2, "0"),
74 );
75
76 return (
77 <Popover open={open} onOpenChange={setOpen}>
78 <PopoverTrigger asChild>
79 <Button
80 type="button"
81 variant="outline"
82 disabled={disabled}
83 className={`w-full justify-start text-left font-normal ${error ? "border-red-500" : ""} ${!value ? "text-gray-500" : ""} ${className}`}
84 >
85 <Clock className="mr-2 h-4 w-4" />
86 {formatDisplayTime() || (
87 <span className="text-gray-500">{placeholder}</span>
88 )}
89 </Button>
90 </PopoverTrigger>
91 <PopoverContent className="w-auto p-4" align="start">
92 <div className="flex items-center gap-4">
93 <div className="flex flex-col gap-2">
94 <label className="text-xs font-medium text-gray-700 dark:text-gray-300">
95 Hour
96 </label>
97 <div className="grid grid-cols-3 gap-1 max-h-48 overflow-y-auto">
98 {hourOptions.map((h) => (
99 <button
100 key={h}
101 type="button"
102 onClick={() => {
103 setHours(h);
104 if (hours && minutes) {
105 handleTimeSelect(h, minutes, ampm);
106 }
107 }}
108 className={`px-3 py-2 text-sm rounded-md transition-colors ${
109 hours === h
110 ? "bg-blue-600 text-white"
111 : "hover:bg-gray-100 dark:hover:bg-gray-700"
112 }`}
113 >
114 {h}
115 </button>
116 ))}
117 </div>
118 </div>
119 <div className="flex flex-col gap-2">
120 <label className="text-xs font-medium text-gray-700 dark:text-gray-300">
121 Minute
122 </label>
123 <div className="grid grid-cols-3 gap-1 max-h-48 overflow-y-auto">
124 {minuteOptions.map((m) => (
125 <button
126 key={m}
127 type="button"
128 onClick={() => {
129 setMinutes(m);
130 if (hours && m) {
131 handleTimeSelect(hours, m, ampm);
132 }
133 }}
134 className={`px-3 py-2 text-sm rounded-md transition-colors ${
135 minutes === m
136 ? "bg-blue-600 text-white"
137 : "hover:bg-gray-100 dark:hover:bg-gray-700"
138 }`}
139 >
140 {m}
141 </button>
142 ))}
143 </div>
144 </div>
145 <div className="flex flex-col gap-2">
146 <label className="text-xs font-medium text-gray-700 dark:text-gray-300">
147 AM/PM
148 </label>
149 <div className="flex flex-col gap-1">
150 <button
151 type="button"
152 onClick={() => {
153 setAmpm("AM");
154 if (hours && minutes) {
155 handleTimeSelect(hours, minutes, "AM");
156 }
157 }}
158 className={`px-4 py-2 text-sm rounded-md transition-colors ${
159 ampm === "AM"
160 ? "bg-blue-600 text-white"
161 : "hover:bg-gray-100 dark:hover:bg-gray-700"
162 }`}
163 >
164 AM
165 </button>
166 <button
167 type="button"
168 onClick={() => {
169 setAmpm("PM");
170 if (hours && minutes) {
171 handleTimeSelect(hours, minutes, "PM");
172 }
173 }}
174 className={`px-4 py-2 text-sm rounded-md transition-colors ${
175 ampm === "PM"
176 ? "bg-blue-600 text-white"
177 : "hover:bg-gray-100 dark:hover:bg-gray-700"
178 }`}
179 >
180 PM
181 </button>
182 </div>
183 </div>
184 </div>
185 {hours && minutes && (
186 <div className="mt-4 pt-4 border-t border-gray-200 dark:border-gray-700">
187 <Button
188 onClick={() => handleTimeSelect(hours, minutes, ampm)}
189 className="w-full"
190 size="sm"
191 >
192 Set Time
193 </Button>
194 </div>
195 )}
196 </PopoverContent>
197 </Popover>
198 );
199 };
200