source: src/main/resources/static/FlightSearch.html@ 8a947b9

Last change on this file since 8a947b9 was 8a947b9, checked in by ste08 <sjovanoska@…>, 3 months ago

Notifications + triggers!

  • Property mode set to 100644
File size: 18.5 KB
Line 
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Flight Search</title>
7 <link rel="stylesheet" href="/css/main.css">
8 <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
9 <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
10 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
11 <link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.0-2/css/all.min.css'>
12 <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet">
13 <style>
14 body {
15 background: url('images/flight.jpg') no-repeat center center fixed;
16 background-size: cover;
17 display: flex;
18 flex-direction: column;
19 align-items: center;
20 justify-content: center;
21 height: 100vh;
22 margin: 0;
23 font-family: Arial, sans-serif;
24 }
25
26 .header {
27 position: absolute;
28 top: 0;
29 left: 0;
30 display: flex;
31 align-items: center;
32 background-color: rebeccapurple;
33 padding: 10px;
34 width: 100%;
35 z-index: 10;
36 }
37 .header img {
38 width: 40px;
39 height: 40px;
40 margin-right: 20px;
41 }
42
43 .header h1 {
44 color: white;
45 margin: 0;
46 font-size: 15px;
47 padding-right: 50px;
48 }
49
50 .header button {
51 background-color: transparent;
52 border: none;
53 color: white;
54 font-size: 14px;
55 }
56
57 .header button:hover {
58 background-color: transparent;
59 cursor: pointer;
60 }
61
62 .split {
63 height: 100%;
64 width: 50%;
65 position: fixed;
66 z-index: 1;
67 top: 0;
68 overflow-x: hidden;
69 padding-top: 20px;
70 }
71
72 .left {
73 left: 0;
74 padding:50px;
75 }
76
77 .right {
78 right: 0;
79 padding:10px;
80 }
81 select {
82 -webkit-appearance: none;
83 -moz-appearance: none;
84 appearance: none;
85 border: 0;
86 outline: 0;
87 font: inherit;
88 width: 20em;
89 height: 3em;
90 padding: 0 4em 0 1em;
91 background: url(https://upload.wikimedia.org/wikipedia/commons/9/9d/Caret_down_font_awesome_whitevariation.svg) no-repeat right 0.8em center/1.4em, linear-gradient(to left, rgba(255, 255, 255, 0.3) 3em, rgba(255, 255, 255, 0.2) 3em);
92 color: white;
93 border-radius: 0.25em;
94 box-shadow: 0 0 1em 0 rgba(0, 0, 0, 0.2);
95 cursor: pointer;
96 }
97 select option {
98 color: inherit;
99 background-color: #320a28;
100 }
101 select:focus {
102 outline: none;
103 }
104 select::-ms-expand {
105 display: none;
106 }
107
108 .search-form-container h2 {
109 color: white;
110 padding-top: 110px;
111 }
112
113 .search-form {
114 padding: 10px;
115 color:white;
116 font-family: Cambria,sans-serif;
117 }
118 .search {
119 background-color: rebeccapurple;
120 color: white;
121 width: 20em;
122 height: 3em;
123 }
124
125 .book {
126 background-color: darkblue;
127 color: white;
128 width: 20em;
129 height: 3em;
130 }
131
132 .search:hover {
133 background-color: mediumpurple;
134 }
135
136 .flights-list-container {
137 color: white;
138 }
139
140 .flights-list-container h2{
141 padding-top: 110px;
142 }
143 .flights-list .flight-item {
144 margin-bottom: 15px;
145 }
146
147 .flights-list .flight-item span {
148 margin-right: 15px;
149 }
150 .flight-item {
151 display: flex;
152 align-items: center;
153 justify-content: space-between;
154 padding: 15px;
155 background: url(https://upload.wikimedia.org/wikipedia/commons/9/9d/Caret_down_font_awesome_whitevariation.svg) no-repeat right 0.8em center/1.4em, linear-gradient(to left, rgba(255, 255, 255, 0.3) 3em, rgba(255, 255, 255, 0.2) 3em);
156 border-radius: 8px;
157 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
158 transition: transform 0.3s ease, background-color 0.3s ease;
159 width: 70%;
160 height: 15px;
161 }
162
163 .popup-overlay {
164 position: fixed;
165 top: 0;
166 left: 0;
167 width: 100%;
168 height: 100%;
169 background: rgba(0, 0, 0, 0.7);
170 display: flex;
171 align-items: center;
172 justify-content: center;
173 z-index: 1000;
174 overflow:hidden;
175 }
176
177 .popup textarea{
178 width: 100%;
179 padding: 10px;
180 margin-top: 10px;
181 border: 1px solid #ccc;
182 border-radius: 4px;
183 resize: vertical;
184 box-sizing: border-box;
185 }
186
187 .popup {
188 background-color: white;
189 padding: 20px;
190 width: 300px;
191 border-radius: 10px;
192 }
193
194 .popup button {
195 margin-top: 10px;
196 background-color: rebeccapurple;
197 color: white;
198 border: none;
199 padding: 5px;
200 cursor: pointer;
201 }
202
203 .popup button:hover {
204 background-color: mediumpurple;
205 }
206
207 .calendar {
208 background-color: transparent;
209 border-radius: 2px;
210 border-color: white;
211 width: 18.6em;
212 height: 2.5em;
213 color:white;
214 }
215
216 .notification-badge {
217 position: absolute;
218 top: 0;
219 right: 0;
220 background-color: red;
221 color: white;
222 border-radius: 50%;
223 padding: 0.3em;
224 font-size: 0.8em;
225 }
226
227 button i {
228 font-size: 24px;
229 color: #333;
230 position: relative;
231 }
232
233 .notifications-popup {
234 position: absolute;
235 top: 50px;
236 right: 0;
237 background: white;
238 border: 1px solid #ddd;
239 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
240 width: 300px;
241 max-height: 400px;
242 overflow-y: auto;
243 padding: 10px;
244 z-index: 9999;
245 }
246
247 .notifications-popup ul {
248 list-style-type: none;
249 padding: 0;
250 }
251
252 .notifications-popup li {
253 padding: 10px;
254 border-bottom: 1px solid #ddd;
255 }
256
257 .notifications-popup button {
258 background-color: #4CAF50; /* Green */
259 color: white;
260 padding: 10px;
261 border: none;
262 cursor: pointer;
263 margin-top: 10px;
264 }
265
266 .notifications-popup button:hover {
267 background-color: #45a049;
268 }
269 </style>
270</head>
271<body>
272
273<div id="app" class="flight-search">
274 <header class="header">
275 <button @click="home"><img src="/images/home.png" alt="Home Icon"></button>
276 <button @click="showReportPopup">Report Issue</button>
277 <button @click="goToWishlistPage">🤍</button>
278 <button @click="goToReports">Monthly Report</button>
279 <button @click="home">Log Out</button>
280
281 <button @click="toggleNotifications">
282 <i class="fas fa-bell"></i>
283 <span v-if="unreadNotifications > 0" class="notification-badge">{{ unreadNotifications }}</span>
284 </button>
285 </header>
286
287 <div class="split left">
288 <div class="search-form-container">
289 <h2>Search flights</h2>
290
291 <div class="search-form">
292 <label for="departure-city">Departure From</label>
293 <br>
294 <select v-model="departureCity" id="departure-city">
295 <option value="" disabled selected>Select a departure city</option>
296 <option v-for="city in cities" :key="city" :value="city">{{ city }}</option>
297 </select>
298
299 <label for="destination">Destination</label>
300 <br>
301 <select v-model="destination" id="destination">
302 <option value="" disabled selected>Select a destination</option>
303 <option v-for="place in places" :key="place" :value="place">{{ place }}</option>
304 </select>
305
306 <label for="departure-date">Departure Date</label>
307 <br>
308 <input class="calendar" type="date" v-model="departureDate" id="departureDate" />
309 <br>
310 <div class="toggle-wrapper">
311 <label for="return-date-toggle">Include Return Date<input type="checkbox" v-model="showReturnDate" id="return-date-toggle" /></label>
312 </div>
313 <br>
314 <div v-if="showReturnDate">
315 <label for="return-date">Return Date</label>
316 <input class="calendar" type="date" v-model="returnDate" id="returnDate" />
317 </div>
318 <br>
319 <button class="search" @click="searchFlights">Search Flights</button>
320 </div>
321 </div>
322 </div>
323 <div class="split right">
324 <div class="flights-list-container" v-show="isContainerVisible" v-if="flights.length">
325 <h2>Available Flights</h2>
326 <div class="flights-list">
327 <div class="flight-item" v-for="flight in flights" :key="flight.flightID">
328 <input type="checkbox" v-model="flight.selected" />
329 <span>{{ flight.departureTime }} | {{ flight.arrivalTime }} | ${{ flight.price }} | {{ flight.availableSeats }}</span>
330
331 <span class="wishlist-heart" @click="toggleWishlist(flight)">
332 {{ flight.wishlisted ? '❤️' : '🤍' }}
333 </span>
334 </div>
335 </div>
336
337 <button v-if="selectedFlights.length" @click="bookFlights" class="book">
338 Book
339 </button>
340 </div>
341 </div>
342
343 <div v-if="showPopup" class="popup-overlay">
344 <div class="popup">
345 <h3>Report an Issue</h3>
346 <textarea v-model="issueDescription" placeholder="Describe the issue here..." rows="5"></textarea>
347 <div class="popup-actions">
348 <button @click="submitIssue" class="submit-btn">Submit</button>
349 <button @click="closePopup" class="cancel-btn">Cancel</button>
350 </div>
351 </div>
352 </div>
353
354 <div v-if="notificationsVisible" class="notifications-popup">
355 <ul>
356 <li v-for="notification in notifications" :key="notification.message">
357 {{ notification.message }}
358 </li>
359 </ul>
360 <button @click="markAllAsRead">Mark All as Read</button>
361 </div>
362</div>
363
364<script>
365 new Vue({
366 el: '#app',
367 data: {
368 isContainerVisible:false,
369 departureCity: '',
370 destination: '',
371 departureDate: '',
372 returnDate: '',
373 flights: [],
374 cities: [],
375 places: [],
376 bookings: [],
377 showReturnDate: false,
378 showPopup: false,
379 issueDescription: '',
380 userId:'',
381 wishlisted:false,
382 unreadNotifications: 0,
383 notifications: [],
384 notificationsVisible: false,
385 },
386 computed: {
387 selectedFlights() {
388 return this.flights.filter(flight => flight.selected);
389 }
390 },
391
392 methods: {
393 toggleNotifications() {
394 this.notificationsVisible = !this.notificationsVisible;
395 },
396 fetchNotifications() {
397 console.log('Fetching notifications for userId:', this.userId);
398 axios.get(`/api/notifications/${this.userId}`)
399 .then(response => {
400 this.notifications = response.data;
401 this.unreadNotifications = this.notifications.filter(n => !n.read).length;
402 })
403 .catch(error => {
404 console.error('Error fetching notifications:', error);
405 });
406 },
407 async searchFlights() {
408 this.isContainerVisible = !this.isContainerVisible;
409 if (this.departureCity && this.destination && this.departureDate) {
410 try {
411 const response = await axios.get('/api/flights/flight-search', {
412 params: {
413 departureCity: this.departureCity,
414 destination: this.destination,
415 departureDate: this.departureDate,
416 returnDate: this.showReturnDate ? this.returnDate : null
417 }
418 });
419
420 if (response.data && response.data.length > 0) {
421 this.flights = response.data;
422 } else {
423 this.flights = [];
424 alert("No flights found for the given criteria.");
425 }
426 } catch (error) {
427 console.error("Error fetching flights", error);
428 }
429 } else {
430 alert("Please select departure city, destination, and date.");
431 this.flights = [];
432 }
433 },
434 bookFlights() {
435 if (!this.selectedFlights.length) {
436 alert("Please select at least one flight.");
437 return;
438 }
439
440 const flight = this.selectedFlights[0];
441 console.log(flight);
442 const totalCost = flight.price;
443
444 const bookingData = {
445 flightId: flight.flightID,
446 bookingDate: new Date().toISOString().split('T')[0],
447 status: 'PENDING',
448 totalCost: totalCost,
449 userId:this.userId
450 };
451 axios.post('/api/bookings', bookingData)
452 .then(response => {
453 const bookingID = response.data.bookingId;
454 alert("Booked successfully!");
455 window.location.href = `/transaction?amount=${encodeURIComponent(totalCost)}&bookingId=${encodeURIComponent(bookingID)}&flightId=${encodeURIComponent(flight.flightID)}&userId=${encodeURIComponent(this.userId)}`;
456 })
457 .catch(error => {
458 console.error("Error booking flight", error);
459 alert("There was an error creating your booking. Please try again.");
460 });
461 },
462 home() {
463 window.location.href = '/';
464 },
465 goToReports(){
466 window.location.href = '/views'
467 },
468 async toggleWishlist(flight) {
469 flight.wishlisted = !flight.wishlisted;
470 this.$set(this.flights, this.flights.indexOf(flight), flight);
471 const wishlistData = {
472 userId: parseInt(this.userId),
473 targetId: flight.flightID
474 };
475 console.log(wishlistData);
476 if (!flight.wishlisted) {
477 try {
478 await axios.delete('/api/wishlists', { params: wishlistData });
479 console.log("Removed from wishlist");
480 } catch (error) {
481 console.error("Error removing from wishlist:", error);
482 }
483 } else {
484 try {
485 await axios.post('/api/wishlists/add', wishlistData);
486 console.log("Added to wishlist");
487 } catch (error) {
488 console.error("Error adding to wishlist:", error);
489 }
490 }
491 },
492 async goToWishlistPage() {
493 window.location.href = `/wishlists?userId=${encodeURIComponent(this.userId)}`;
494 },
495 showReportPopup() {
496 this.showPopup = true;
497 },
498 closePopup() {
499 this.showPopup = false;
500 },
501 submitIssue() {
502 if (this.issueDescription.trim()) {
503 const reviewData = {
504 userId: this.userId,
505 subject: "Issue Report",
506 description: this.issueDescription
507 };
508
509 axios.post('/api/support-tickets', reviewData)
510 .then(response => {
511 alert("Issue reported successfully!");
512 this.closePopup();
513 })
514 .catch(error => {
515 console.error("Error submitting issue:", error);
516 alert("There was an error submitting your issue. Please try again.");
517 });
518 } else {
519 alert("Please enter a description for the issue.");
520 }
521 },
522 async fetchFlights() {
523 try {
524 const response = await axios.get('/api/destinations');
525 this.cities = response.data.map(departureCity => departureCity.name);
526 this.places = response.data.map(destination => destination.name);
527 } catch (error) {
528 console.error("Error fetching Destinations", error);
529 }
530 }
531 },
532 mounted() {
533 this.fetchFlights();
534 const params = new URLSearchParams(window.location.search);
535 this.userId = params.get("userId");
536 axios.get('api/flights')
537 .then(response => {
538 this.flights = response.data;
539 })
540 .catch(error => {
541 console.error("Error fetching flights", error);
542 });
543 this.fetchNotifications();
544 }
545 });
546</script>
547
548</body>
549</html>
Note: See TracBrowser for help on using the repository browser.