PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.13
Yatra – Travel Booking & Tour Operator Software v3.0.13
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.13, at resources/js/components/dashboard/UpcomingDepartures.tsx

171 lines 6.0 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 | null) => {
39 const date = toDateValue(dateString);
40 if (isNaN(date.getTime())) return "-";
41 return date.toLocaleDateString("en-US", {
42 month: "short",
43 day: "numeric",
44 year: "numeric",
45 });
46 };
47
48 const getDaysUntil = (dateString?: string | null) => {
49 const today = new Date();
50 const departure = toDateValue(dateString);
51 if (isNaN(departure.getTime())) return null;
52 const diffTime = departure.getTime() - today.getTime();
53 const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
54 return diffDays;
55 };
56
57 if (loading) {
58 return (
59 <Card>
60 <CardHeader>
61 <CardTitle>{__("Upcoming Departures", "yatra")}</CardTitle>
62 </CardHeader>
63 <CardContent>
64 <div className="text-sm text-gray-500 dark:text-gray-400">
65 {__("Loading...", "yatra")}
66 </div>
67 </CardContent>
68 </Card>
69 );
70 }
71
72 return (
73 <Card>
74 <CardHeader>
75 <CardTitle>{__("Upcoming Departures", "yatra")}</CardTitle>
76 </CardHeader>
77 <CardContent>
78 {departures && departures.length > 0 ? (
79 <div className="space-y-2">
80 {departures.slice(0, 5).map((departure) => {
81 const daysUntil = getDaysUntil(departure.departure_date);
82 const total = Number(departure.total_spots) || 0;
83 let occupancy = 0;
84 if (total > 0) {
85 occupancy =
86 ((total - Number(departure.available_spots || 0)) / total) *
87 100;
88 }
89 // Clamp to [0, 100] to avoid negative or >100 values
90 occupancy = Math.max(0, Math.min(100, occupancy));
91
92 return (
93 <div
94 key={departure.id}
95 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"
96 onClick={() => {
97 if (onView) {
98 onView(departure);
99 return;
100 }
101 const admin = (window as any)?.yatraAdmin;
102 const baseUrl = admin?.siteUrl || "";
103 const tripId = departure.trip_id || "";
104 const query = `?page=yatra&subpage=departures&action=view&id=${departure.id}${tripId ? `&trip_id=${tripId}` : ""}`;
105 window.location.href = `${baseUrl}/wp-admin/admin.php${query}`;
106 }}
107 >
108 <div className="flex items-start justify-between mb-2">
109 <div className="flex-1">
110 <h4 className="font-medium text-gray-900 dark:text-white mb-1">
111 {departure.trip_title}
112 </h4>
113 {departure.destination && (
114 <div className="flex items-center gap-1 text-sm text-gray-500 dark:text-gray-400 mb-2">
115 <MapPin className="w-3 h-3" />
116 <span>{departure.destination}</span>
117 </div>
118 )}
119 </div>
120 {daysUntil !== null && daysUntil >= 0 && daysUntil <= 7 && (
121 <Badge variant={daysUntil <= 3 ? "error" : "warning"}>
122 {daysUntil === 0
123 ? __("Today", "yatra")
124 : daysUntil === 1
125 ? __("Tomorrow", "yatra")
126 : `${daysUntil} ${__("days", "yatra")}`}
127 </Badge>
128 )}
129 </div>
130
131 <div className="flex items-center gap-4 text-sm text-gray-600 dark:text-gray-400">
132 <div className="flex items-center gap-1">
133 <Calendar className="w-4 h-4" />
134 <span>{formatDate(departure.departure_date)}</span>
135 </div>
136 <div className="flex items-center gap-1">
137 <Users className="w-4 h-4" />
138 <span>
139 {departure.available_spots} / {departure.total_spots}{" "}
140 {__("available", "yatra")}
141 </span>
142 </div>
143 </div>
144
145 <div className="mt-2">
146 <div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
147 <div
148 className="bg-blue-600 h-2 rounded-full transition-all"
149 style={{ width: `${occupancy}%` }}
150 />
151 </div>
152 <span className="text-xs text-gray-500 dark:text-gray-400 mt-1 block">
153 {occupancy.toFixed(0)}% {__("occupied", "yatra")}
154 </span>
155 </div>
156 </div>
157 );
158 })}
159 </div>
160 ) : (
161 <p className="text-sm text-gray-500 dark:text-gray-400">
162 {__("No upcoming departures", "yatra")}
163 </p>
164 )}
165 </CardContent>
166 </Card>
167 );
168 };
169
170 export default UpcomingDepartures;
171