PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.9
Yatra – Travel Booking & Tour Operator Software v3.0.9
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
yatra / resources / js / components / dashboard / UpcomingDepartures.tsx

UpcomingDepartures.tsx in Yatra – Travel Booking & Tour Operator Software 3.0.9, at resources/js/components/dashboard/UpcomingDepartures.tsx

169 lines 5.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Upcoming Departures Widget
3 * Displays trips with upcoming departure dates
4 */
5
6 import React from "react";
7 import { __ } from "../../lib/i18n";
8 import { toDateValue } from "../../lib/dateFormat";
9 import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
10 import { Badge } from "../ui/badge";
11 import { Calendar, MapPin, Users } from "lucide-react";
12
13 interface Departure {
14 id: number;
15 trip_id?: number;
16 trip_title: string;
17 departure_date: string;
18 available_spots: number;
19 total_spots: number;
20 status: string;
21 destination?: string;
22 }
23
24 interface UpcomingDeparturesProps {
25 departures: Departure[];
26 loading?: boolean;
27 onView?: (departure: Departure) => void;
28 }
29
30 /**
31 * Upcoming Departures Widget
32 */
33 export const UpcomingDepartures: React.FC<UpcomingDeparturesProps> = ({
34 departures,
35 loading = false,
36 onView,
37 }) => {
38 const formatDate = (dateString: string) => {
39 const date = toDateValue(dateString);
40 return date.toLocaleDateString("en-US", {
41 month: "short",
42 day: "numeric",
43 year: "numeric",
44 });
45 };
46
47 const getDaysUntil = (dateString: string) => {
48 const today = new Date();
49 const departure = toDateValue(dateString);
50 const diffTime = departure.getTime() - today.getTime();
51 const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
52 return diffDays;
53 };
54
55 if (loading) {
56 return (
57 <Card>
58 <CardHeader>
59 <CardTitle>{__("Upcoming Departures", "yatra")}</CardTitle>
60 </CardHeader>
61 <CardContent>
62 <div className="text-sm text-gray-500 dark:text-gray-400">
63 {__("Loading...", "yatra")}
64 </div>
65 </CardContent>
66 </Card>
67 );
68 }
69
70 return (
71 <Card>
72 <CardHeader>
73 <CardTitle>{__("Upcoming Departures", "yatra")}</CardTitle>
74 </CardHeader>
75 <CardContent>
76 {departures && departures.length > 0 ? (
77 <div className="space-y-2">
78 {departures.slice(0, 5).map((departure) => {
79 const daysUntil = getDaysUntil(departure.departure_date);
80 const total = Number(departure.total_spots) || 0;
81 let occupancy = 0;
82 if (total > 0) {
83 occupancy =
84 ((total - Number(departure.available_spots || 0)) / total) *
85 100;
86 }
87 // Clamp to [0, 100] to avoid negative or >100 values
88 occupancy = Math.max(0, Math.min(100, occupancy));
89
90 return (
91 <div
92 key={departure.id}
93 className="p-3 rounded-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors cursor-pointer"
94 onClick={() => {
95 if (onView) {
96 onView(departure);
97 return;
98 }
99 const admin = (window as any)?.yatraAdmin;
100 const baseUrl = admin?.siteUrl || "";
101 const tripId = departure.trip_id || "";
102 const query = `?page=yatra&subpage=departures&action=view&id=${departure.id}${tripId ? `&trip_id=${tripId}` : ""}`;
103 window.location.href = `${baseUrl}/wp-admin/admin.php${query}`;
104 }}
105 >
106 <div className="flex items-start justify-between mb-2">
107 <div className="flex-1">
108 <h4 className="font-medium text-gray-900 dark:text-white mb-1">
109 {departure.trip_title}
110 </h4>
111 {departure.destination && (
112 <div className="flex items-center gap-1 text-sm text-gray-500 dark:text-gray-400 mb-2">
113 <MapPin className="w-3 h-3" />
114 <span>{departure.destination}</span>
115 </div>
116 )}
117 </div>
118 {daysUntil >= 0 && daysUntil <= 7 && (
119 <Badge variant={daysUntil <= 3 ? "error" : "warning"}>
120 {daysUntil === 0
121 ? __("Today", "yatra")
122 : daysUntil === 1
123 ? __("Tomorrow", "yatra")
124 : `${daysUntil} ${__("days", "yatra")}`}
125 </Badge>
126 )}
127 </div>
128
129 <div className="flex items-center gap-4 text-sm text-gray-600 dark:text-gray-400">
130 <div className="flex items-center gap-1">
131 <Calendar className="w-4 h-4" />
132 <span>{formatDate(departure.departure_date)}</span>
133 </div>
134 <div className="flex items-center gap-1">
135 <Users className="w-4 h-4" />
136 <span>
137 {departure.available_spots} / {departure.total_spots}{" "}
138 {__("available", "yatra")}
139 </span>
140 </div>
141 </div>
142
143 <div className="mt-2">
144 <div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
145 <div
146 className="bg-blue-600 h-2 rounded-full transition-all"
147 style={{ width: `${occupancy}%` }}
148 />
149 </div>
150 <span className="text-xs text-gray-500 dark:text-gray-400 mt-1 block">
151 {occupancy.toFixed(0)}% {__("occupied", "yatra")}
152 </span>
153 </div>
154 </div>
155 );
156 })}
157 </div>
158 ) : (
159 <p className="text-sm text-gray-500 dark:text-gray-400">
160 {__("No upcoming departures", "yatra")}
161 </p>
162 )}
163 </CardContent>
164 </Card>
165 );
166 };
167
168 export default UpcomingDepartures;
169