PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.8
Yatra – Travel Booking & Tour Operator Software v3.0.2.8
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
yatra / resources / js / pages / account / Dashboard.tsx

Dashboard.tsx in Yatra – Travel Booking & Tour Operator Software 3.0.2.8, at resources/js/pages/account/Dashboard.tsx

525 lines 24.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import React from "react";
2 import {
3 Calendar,
4 CreditCard,
5 User,
6 LifeBuoy,
7 FileText,
8 Package,
9 MapPin,
10 ChevronRight,
11 ArrowRight,
12 Sparkles,
13 Plane,
14 Clock,
15 CheckCircle2,
16 AlertCircle,
17 Bell,
18 } from "lucide-react";
19 import { __ } from "../../lib/i18n";
20 import { formatDate, getBadge, currency, phoneToTelHref } from "./utils";
21 import type { Booking, Payment, CustomerProfile } from "./types";
22
23 interface DashboardProps {
24 bookings: Booking[];
25 payments: Payment[];
26 displayProfile: CustomerProfile | null;
27 stats: Array<{
28 label: string;
29 value: string | number;
30 icon: React.ElementType;
31 badge?: string;
32 }>;
33 notifications: any[];
34 /** From Yatra Settings → General (company phone). */
35 conciergePhone?: string;
36 conciergeEmail?: string;
37 onSectionChange: (section: string) => void;
38 }
39
40 const Dashboard: React.FC<DashboardProps> = ({
41 bookings,
42 payments,
43 displayProfile,
44 stats,
45 notifications,
46 conciergePhone = "",
47 conciergeEmail = "",
48 onSectionChange,
49 }) => {
50 const conciergeTel = phoneToTelHref(conciergePhone);
51 const conciergeMail = String(conciergeEmail || "").trim();
52 const upcomingBookings =
53 bookings.filter((b) => new Date(b.travel_date) > new Date()).length > 0
54 ? bookings.filter((b) => new Date(b.travel_date) > new Date()).slice(0, 3)
55 : bookings
56 .filter((b) => new Date(b.travel_date) > new Date())
57 .slice(0, 3);
58
59 const recentBookings = bookings.length > 0 ? bookings.slice(0, 2) : [];
60
61 const pendingPayments =
62 payments.filter((p) => p.status === "pending").length > 0
63 ? payments.filter((p) => p.status === "pending").slice(0, 2)
64 : [];
65
66 const displayNotifications = notifications;
67
68 // Enhanced stats with colors and gradients
69 const enhancedStats = [
70 {
71 ...stats[0],
72 gradient: "from-blue-500 to-blue-600",
73 bgColor: "bg-blue-50 dark:bg-blue-900/20",
74 iconColor: "text-blue-600 dark:text-blue-400",
75 },
76 {
77 ...stats[1],
78 gradient: "from-emerald-500 to-emerald-600",
79 bgColor: "bg-emerald-50 dark:bg-emerald-900/20",
80 iconColor: "text-emerald-600 dark:text-emerald-400",
81 },
82 {
83 ...stats[2],
84 gradient: "from-amber-500 to-amber-600",
85 bgColor: "bg-amber-50 dark:bg-amber-900/20",
86 iconColor: "text-amber-600 dark:text-amber-400",
87 },
88 {
89 ...stats[3],
90 gradient: "from-purple-500 to-purple-600",
91 bgColor: "bg-purple-50 dark:bg-purple-900/20",
92 iconColor: "text-purple-600 dark:text-purple-400",
93 },
94 ];
95
96 return (
97 <div className="space-y-6">
98 {/* Welcome Banner */}
99 <div
100 className="yatra-dashboard-welcome relative overflow-hidden rounded-2xl p-8 shadow-xl"
101 style={{
102 background:
103 "linear-gradient(135deg, #2563eb 0%, #1d4ed8 50%, #4f46e5 100%)",
104 color: "#ffffff",
105 }}
106 >
107 <div className="relative z-10">
108 <div className="flex items-start justify-between">
109 <div>
110 <h2
111 className="text-lg font-bold mb-2"
112 style={{ color: "#ffffff" }}
113 >
114 {__("Welcome back,", "yatra")}{" "}
115 {displayProfile?.name?.split(" ")[0] || __("Traveler", "yatra")}
116 ! 👋
117 </h2>
118 <p className="text-sm mb-4" style={{ color: "#e0e7ff" }}>
119 {__("You have", "yatra")} {upcomingBookings.length}{" "}
120 {upcomingBookings.length === 1
121 ? __("upcoming adventure", "yatra")
122 : __("upcoming adventures", "yatra")}{" "}
123 {__("coming up", "yatra")}
124 </p>
125 <div className="flex flex-wrap gap-3 mt-4">
126 <div
127 role="button"
128 tabIndex={0}
129 onClick={() => onSectionChange("bookings")}
130 className="px-4 py-2 rounded-lg text-sm font-medium transition-all flex items-center gap-2 hover:opacity-90 cursor-pointer"
131 style={{
132 backgroundColor: "rgba(255, 255, 255, 0.2)",
133 color: "#ffffff",
134 backdropFilter: "blur(8px)",
135 }}
136 >
137 <Calendar className="w-4 h-4" style={{ color: "#ffffff" }} />
138 {__("View Calendar", "yatra")}
139 </div>
140 <div
141 role="button"
142 tabIndex={0}
143 onClick={() => onSectionChange("documents")}
144 className="px-4 py-2 rounded-lg text-sm font-medium transition-all flex items-center gap-2 hover:opacity-90 cursor-pointer"
145 style={{
146 backgroundColor: "rgba(255, 255, 255, 0.2)",
147 color: "#ffffff",
148 backdropFilter: "blur(8px)",
149 }}
150 >
151 <FileText className="w-4 h-4" style={{ color: "#ffffff" }} />
152 {__("My Documents", "yatra")}
153 </div>
154 </div>
155 </div>
156 <div className="hidden md:block">
157 <div
158 className="w-24 h-24 rounded-full flex items-center justify-center"
159 style={{
160 backgroundColor: "rgba(255, 255, 255, 0.1)",
161 backdropFilter: "blur(8px)",
162 }}
163 >
164 <Plane className="w-12 h-12" style={{ color: "#ffffff" }} />
165 </div>
166 </div>
167 </div>
168 </div>
169 <div
170 className="absolute top-0 right-0 w-64 h-64 rounded-full -mr-32 -mt-32"
171 style={{ backgroundColor: "rgba(255, 255, 255, 0.05)" }}
172 ></div>
173 <div
174 className="absolute bottom-0 left-0 w-48 h-48 rounded-full -ml-24 -mb-24"
175 style={{ backgroundColor: "rgba(255, 255, 255, 0.05)" }}
176 ></div>
177 </div>
178
179 {/* Stats Grid */}
180 <div className="yatra-dashboard-stats grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
181 {enhancedStats.map((stat) => (
182 <div
183 key={stat.label}
184 className={`yatra-stat-card yatra-stat-card-${stat.label.toLowerCase().replace(/\s+/g, "-")} group relative bg-white dark:bg-gray-800 border border-gray-100 dark:border-gray-700 rounded-xl shadow-sm hover:shadow-md transition-all overflow-hidden`}
185 >
186 <div className="p-6">
187 <div className="flex items-start justify-between mb-4">
188 <div className={`p-3 rounded-lg ${stat.bgColor}`}>
189 <stat.icon className={`w-6 h-6 ${stat.iconColor}`} />
190 </div>
191 {stat.badge && (
192 <span className="inline-flex text-xs px-2 py-1 rounded-full bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-200 font-medium">
193 {stat.badge}
194 </span>
195 )}
196 </div>
197 <div>
198 <p className="text-sm text-gray-500 dark:text-gray-400 mb-1">
199 {stat.label}
200 </p>
201 <p className="text-xl font-bold text-gray-900 dark:text-white">
202 {stat.value}
203 </p>
204 </div>
205 </div>
206 <div
207 className={`absolute bottom-0 left-0 right-0 h-1 bg-gradient-to-r ${stat.gradient} opacity-0 group-hover:opacity-100 transition-opacity`}
208 ></div>
209 </div>
210 ))}
211 </div>
212
213 {/* Main Content Grid */}
214 <div className="grid gap-6 lg:grid-cols-3">
215 {/* Upcoming Trips - Takes 2 columns */}
216 <div className="yatra-dashboard-upcoming-trips lg:col-span-2 bg-white dark:bg-gray-800 border border-gray-100 dark:border-gray-700 rounded-xl shadow-sm overflow-hidden">
217 <div className="yatra-dashboard-upcoming-trips-header p-6 border-b border-gray-100 dark:border-gray-700">
218 <div className="flex items-center justify-between">
219 <div className="flex items-center gap-3">
220 <div className="p-2 bg-emerald-50 dark:bg-emerald-900/20 rounded-lg">
221 <Calendar className="w-5 h-5 text-emerald-600 dark:text-emerald-400" />
222 </div>
223 <div>
224 <h3 className="text-base font-semibold text-gray-900 dark:text-white">
225 {__("Upcoming Trips", "yatra")}
226 </h3>
227 <p className="text-sm text-gray-500 dark:text-gray-400">
228 {__("Your next adventures", "yatra")}
229 </p>
230 </div>
231 </div>
232 {upcomingBookings.length > 0 && (
233 <div
234 role="button"
235 tabIndex={0}
236 onClick={() => onSectionChange("bookings")}
237 className="text-sm text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-medium flex items-center gap-1 transition-colors cursor-pointer"
238 >
239 {__("View all", "yatra")}
240 <ArrowRight className="w-4 h-4" />
241 </div>
242 )}
243 </div>
244 </div>
245 <div className="p-6">
246 {upcomingBookings.length > 0 ? (
247 <div className="space-y-4">
248 {upcomingBookings.map((booking) => (
249 <div
250 key={booking.id}
251 className="yatra-booking-card yatra-booking-card-upcoming group relative border border-gray-200 dark:border-gray-700 rounded-xl p-5 hover:border-blue-300 dark:hover:border-blue-600 hover:shadow-md transition-all cursor-pointer bg-gradient-to-r from-white to-gray-50/50 dark:from-gray-800 dark:to-gray-800/50"
252 >
253 <div className="flex items-start gap-4">
254 <div className="flex-shrink-0">
255 <div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-lg flex items-center justify-center shadow-sm">
256 <MapPin className="w-6 h-6 text-white" />
257 </div>
258 </div>
259 <div className="flex-1 min-w-0">
260 <div className="flex items-start justify-between gap-2 mb-2">
261 <h4 className="text-sm font-semibold text-gray-900 dark:text-white group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors">
262 {booking.trip_title}
263 </h4>
264 <span className={getBadge(booking.booking_status)}>
265 {__(booking.booking_status, booking.booking_status)}
266 </span>
267 </div>
268 <p className="text-sm text-gray-600 dark:text-gray-400 mb-2 flex items-center gap-2">
269 <MapPin className="w-4 h-4" />
270 {booking.destination ||
271 __("Multiple destinations", "yatra")}
272 </p>
273 <div className="flex items-center gap-4 text-xs text-gray-500 dark:text-gray-400">
274 <span className="flex items-center gap-1">
275 <Calendar className="w-3.5 h-3.5" />
276 {formatDate(booking.travel_date)}
277 </span>
278 <span className="flex items-center gap-1">
279 <User className="w-3.5 h-3.5" />
280 {booking.travelers}{" "}
281 {booking.travelers === 1
282 ? __("Traveler", "yatra")
283 : __("Travelers", "yatra")}
284 </span>
285 </div>
286 </div>
287 <ChevronRight className="w-5 h-5 text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors flex-shrink-0" />
288 </div>
289 </div>
290 ))}
291 </div>
292 ) : (
293 <div className="text-center py-12">
294 <div className="w-16 h-16 bg-gray-100 dark:bg-gray-700 rounded-full flex items-center justify-center mx-auto mb-4">
295 <Calendar className="w-8 h-8 text-gray-400" />
296 </div>
297 <p className="text-gray-500 dark:text-gray-400 font-medium mb-1">
298 {__("No upcoming trips", "yatra")}
299 </p>
300 <p className="text-sm text-gray-400 dark:text-gray-500">
301 {__("Start planning your next adventure!", "yatra")}
302 </p>
303 </div>
304 )}
305 </div>
306 </div>
307
308 {/* Quick Actions & Notifications - Takes 1 column */}
309 <div className="space-y-6">
310 {/* Quick Actions */}
311 <div className="yatra-dashboard-quick-actions bg-white dark:bg-gray-800 border border-gray-100 dark:border-gray-700 rounded-xl shadow-sm overflow-hidden">
312 <div className="yatra-dashboard-quick-actions-header p-6 border-b border-gray-100 dark:border-gray-700">
313 <div className="flex items-center gap-3">
314 <div className="p-2 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
315 <Sparkles className="w-5 h-5 text-purple-600 dark:text-purple-400" />
316 </div>
317 <h3 className="text-base font-semibold text-gray-900 dark:text-white">
318 {__("Quick Actions", "yatra")}
319 </h3>
320 </div>
321 </div>
322 <div className="p-6 space-y-3">
323 <div
324 role="button"
325 tabIndex={0}
326 onClick={() => onSectionChange("bookings")}
327 className="w-full flex items-center justify-between p-4 border border-gray-200 dark:border-gray-700 rounded-lg hover:border-blue-300 dark:hover:border-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/10 transition-all group cursor-pointer text-sm"
328 >
329 <div className="flex items-center gap-3">
330 <Package className="w-5 h-5 text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-400" />
331 <span className="font-medium text-gray-700 dark:text-gray-300 group-hover:text-blue-600 dark:group-hover:text-blue-400">
332 {__("View Bookings", "yatra")}
333 </span>
334 </div>
335 <ChevronRight className="w-4 h-4 text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-400" />
336 </div>
337 <div
338 role="button"
339 tabIndex={0}
340 onClick={() => onSectionChange("payments")}
341 className="w-full flex items-center justify-between p-4 border border-gray-200 dark:border-gray-700 rounded-lg hover:border-blue-300 dark:hover:border-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/10 transition-all group cursor-pointer text-sm"
342 >
343 <div className="flex items-center gap-3">
344 <CreditCard className="w-5 h-5 text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-400" />
345 <span className="font-medium text-gray-700 dark:text-gray-300 group-hover:text-blue-600 dark:group-hover:text-blue-400">
346 {__("Make Payment", "yatra")}
347 </span>
348 </div>
349 <ChevronRight className="w-4 h-4 text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-400" />
350 </div>
351 <div
352 role="button"
353 tabIndex={0}
354 onClick={() => onSectionChange("documents")}
355 className="w-full flex items-center justify-between p-4 border border-gray-200 dark:border-gray-700 rounded-lg hover:border-blue-300 dark:hover:border-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/10 transition-all group cursor-pointer text-sm"
356 >
357 <div className="flex items-center gap-3">
358 <FileText className="w-5 h-5 text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-400" />
359 <span className="font-medium text-gray-700 dark:text-gray-300 group-hover:text-blue-600 dark:group-hover:text-blue-400">
360 {__("My Documents", "yatra")}
361 </span>
362 </div>
363 <ChevronRight className="w-4 h-4 text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-400" />
364 </div>
365 {conciergeTel ? (
366 <a
367 href={conciergeTel}
368 className="w-full flex items-center justify-between p-4 border border-gray-200 dark:border-gray-700 rounded-lg hover:border-blue-300 dark:hover:border-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/10 transition-all group cursor-pointer text-sm no-underline text-inherit"
369 >
370 <div className="flex items-center gap-3">
371 <LifeBuoy className="w-5 h-5 text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-400" />
372 <span className="font-medium text-gray-700 dark:text-gray-300 group-hover:text-blue-600 dark:group-hover:text-blue-400">
373 {__("Call us", "yatra")}
374 </span>
375 </div>
376 <ChevronRight className="w-4 h-4 text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-400" />
377 </a>
378 ) : conciergeMail ? (
379 <a
380 href={`mailto:${encodeURIComponent(conciergeMail)}`}
381 className="w-full flex items-center justify-between p-4 border border-gray-200 dark:border-gray-700 rounded-lg hover:border-blue-300 dark:hover:border-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/10 transition-all group cursor-pointer text-sm no-underline text-inherit"
382 >
383 <div className="flex items-center gap-3">
384 <LifeBuoy className="w-5 h-5 text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-400" />
385 <span className="font-medium text-gray-700 dark:text-gray-300 group-hover:text-blue-600 dark:group-hover:text-blue-400">
386 {__("Email us", "yatra")}
387 </span>
388 </div>
389 <ChevronRight className="w-4 h-4 text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-400" />
390 </a>
391 ) : null}
392 </div>
393 </div>
394
395 {/* Notifications */}
396 <div className="yatra-dashboard-notifications bg-white dark:bg-gray-800 border border-gray-100 dark:border-gray-700 rounded-xl shadow-sm overflow-hidden">
397 <div className="yatra-dashboard-notifications-header p-6 border-b border-gray-100 dark:border-gray-700">
398 <div className="flex items-center gap-3">
399 <div className="p-2 bg-amber-50 dark:bg-amber-900/20 rounded-lg">
400 <Bell className="w-5 h-5 text-amber-600 dark:text-amber-400" />
401 </div>
402 <div>
403 <h3 className="text-base font-semibold text-gray-900 dark:text-white">
404 {__("Notifications", "yatra")}
405 </h3>
406 <p className="text-xs text-gray-500 dark:text-gray-400">
407 {__("Updates & reminders", "yatra")}
408 </p>
409 </div>
410 </div>
411 </div>
412 <div className="p-6 space-y-3">
413 {displayNotifications.length > 0 ? (
414 displayNotifications.map((notif) => (
415 <div
416 key={notif.id}
417 className={`p-4 rounded-lg border ${
418 notif.type === "warning"
419 ? "bg-amber-50 dark:bg-amber-900/10 border-amber-200 dark:border-amber-800"
420 : "bg-blue-50 dark:bg-blue-900/10 border-blue-200 dark:border-blue-800"
421 }`}
422 >
423 <div className="flex items-start gap-3">
424 {notif.type === "warning" ? (
425 <AlertCircle className="w-5 h-5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5" />
426 ) : (
427 <CheckCircle2 className="w-5 h-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
428 )}
429 <div className="flex-1 min-w-0">
430 <p className="font-semibold text-sm text-gray-900 dark:text-white mb-1">
431 {notif.title}
432 </p>
433 <p className="text-xs text-gray-600 dark:text-gray-400 leading-relaxed">
434 {notif.message}
435 </p>
436 </div>
437 </div>
438 </div>
439 ))
440 ) : (
441 <div className="text-center py-6">
442 <Bell className="w-8 h-8 text-gray-300 dark:text-gray-600 mx-auto mb-2" />
443 <p className="text-sm text-gray-400 dark:text-gray-500">
444 {__("No new notifications", "yatra")}
445 </p>
446 </div>
447 )}
448 </div>
449 </div>
450 </div>
451 </div>
452
453 {/* Recent Activity Section */}
454 {(recentBookings.length > 0 || pendingPayments.length > 0) && (
455 <div className="yatra-dashboard-recent-activity bg-white dark:bg-gray-800 border border-gray-100 dark:border-gray-700 rounded-xl shadow-sm overflow-hidden">
456 <div className="yatra-dashboard-recent-activity-header p-6 border-b border-gray-100 dark:border-gray-700">
457 <div className="flex items-center gap-3">
458 <div className="p-2 bg-indigo-50 dark:bg-indigo-900/20 rounded-lg">
459 <Clock className="w-5 h-5 text-indigo-600 dark:text-indigo-400" />
460 </div>
461 <div>
462 <h3 className="text-base font-semibold text-gray-900 dark:text-white">
463 {__("Recent Activity", "yatra")}
464 </h3>
465 <p className="text-sm text-gray-500 dark:text-gray-400">
466 {__("Your latest updates", "yatra")}
467 </p>
468 </div>
469 </div>
470 </div>
471 <div className="p-6">
472 <div className="space-y-4">
473 {recentBookings.map((booking) => (
474 <div
475 key={booking.id}
476 className="flex items-center gap-4 pb-4 border-b border-gray-100 dark:border-gray-700 last:border-0 last:pb-0"
477 >
478 <div className="w-10 h-10 bg-blue-100 dark:bg-blue-900/30 rounded-full flex items-center justify-center flex-shrink-0">
479 <CheckCircle2 className="w-5 h-5 text-blue-600 dark:text-blue-400" />
480 </div>
481 <div className="flex-1 min-w-0">
482 <p className="text-sm font-medium text-gray-900 dark:text-white">
483 {__("Booking confirmed:", "yatra")} {booking.trip_title}
484 </p>
485 <p className="text-xs text-gray-500 dark:text-gray-400">
486 {formatDate(booking.booking_date || booking.created_at)}
487 </p>
488 </div>
489 <span className={getBadge(booking.booking_status)}>
490 {__(booking.booking_status, booking.booking_status)}
491 </span>
492 </div>
493 ))}
494 {pendingPayments.map((payment) => (
495 <div
496 key={payment.id}
497 className="flex items-center gap-4 pb-4 border-b border-gray-100 dark:border-gray-700 last:border-0 last:pb-0"
498 >
499 <div className="w-10 h-10 bg-amber-100 dark:bg-amber-900/30 rounded-full flex items-center justify-center flex-shrink-0">
500 <Clock className="w-5 h-5 text-amber-600 dark:text-amber-400" />
501 </div>
502 <div className="flex-1 min-w-0">
503 <p className="text-sm font-medium text-gray-900 dark:text-white">
504 {__("Payment pending:", "yatra")}{" "}
505 {currency(payment.amount)}
506 </p>
507 <p className="text-xs text-gray-500 dark:text-gray-400">
508 {formatDate(payment.date)}
509 </p>
510 </div>
511 <span className={getBadge(payment.status)}>
512 {__(payment.status, payment.status)}
513 </span>
514 </div>
515 ))}
516 </div>
517 </div>
518 </div>
519 )}
520 </div>
521 );
522 };
523
524 export default Dashboard;
525