source: src/main/resources/static/FlightSearch.html@ 7deb3e2

Last change on this file since 7deb3e2 was 7deb3e2, checked in by ste08 <sjovanoska@…>, 8 weeks ago

notification status + review page fixed

  • Property mode set to 100644
File size: 18.7 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="goToReviews">Reviews</button>
280 <button @click="home">Log Out</button>
281
282 <button @click="toggleNotifications">
283 <i class="fas fa-bell"></i>
284 <span v-if="unreadNotifications > 0" class="notification-badge">{{ unreadNotifications }}</span>
285 </button>
286 </header>
287
288 <div class="split left">
289 <div class="search-form-container">
290 <h2>Search flights</h2>
291
292 <div class="search-form">
293 <label for="departure-city">Departure From</label>
294 <br>
295 <select v-model="departureCity" id="departure-city">
296 <option value="" disabled selected>Select a departure city</option>
297 <option v-for="city in cities" :key="city" :value="city">{{ city }}</option>
298 </select>
299
300 <label for="destination">Destination</label>
301 <br>
302 <select v-model="destination" id="destination">
303 <option value="" disabled selected>Select a destination</option>
304 <option v-for="place in places" :key="place" :value="place">{{ place }}</option>
305 </select>
306
307 <label for="departure-date">Departure Date</label>
308 <br>
309 <input class="calendar" type="date" v-model="departureDate" id="departureDate" />
310 <br>
311 <div class="toggle-wrapper">
312 <label for="return-date-toggle">Include Return Date<input type="checkbox" v-model="showReturnDate" id="return-date-toggle" /></label>
313 </div>
314 <br>
315 <div v-if="showReturnDate">
316 <label for="return-date">Return Date</label>
317 <input class="calendar" type="date" v-model="returnDate" id="returnDate" />
318 </div>
319 <br>
320 <button class="search" @click="searchFlights">Search Flights</button>
321 </div>
322 </div>
323 </div>
324 <div class="split right">
325 <div class="flights-list-container" v-show="isContainerVisible" v-if="flights.length">
326 <h2>Available Flights</h2>
327 <div class="flights-list">
328 <div class="flight-item" v-for="flight in flights" :key="flight.flightID">
329 <input type="checkbox" v-model="flight.selected" />
330 <span>{{ flight.departureTime }} | {{ flight.arrivalTime }} | ${{ flight.price }} | {{ flight.availableSeats }}</span>
331
332 <span class="wishlist-heart" @click="toggleWishlist(flight)">
333 {{ flight.wishlisted ? '❤️' : '🤍' }}
334 </span>
335 </div>
336 </div>
337
338 <button v-if="selectedFlights.length" @click="bookFlights" class="book">
339 Book
340 </button>
341 </div>
342 </div>
343
344 <div v-if="showPopup" class="popup-overlay">
345 <div class="popup">
346 <h3>Report an Issue</h3>
347 <textarea v-model="issueDescription" placeholder="Describe the issue here..." rows="5"></textarea>
348 <div class="popup-actions">
349 <button @click="submitIssue" class="submit-btn">Submit</button>
350 <button @click="closePopup" class="cancel-btn">Cancel</button>
351 </div>
352 </div>
353 </div>
354
355 <div v-if="notificationsVisible" class="notifications-popup">
356 <ul>
357 <li v-for="notification in notifications" :key="notification.message">
358 {{ notification.message }} - {{ "RESOLVED" }}
359 </li>
360 </ul>
361 <button @click="markAllAsRead">Mark All as Read</button>
362 </div>
363</div>
364
365<script>
366 new Vue({
367 el: '#app',
368 data: {
369 isContainerVisible:false,
370 departureCity: '',
371 destination: '',
372 departureDate: '',
373 returnDate: '',
374 flights: [],
375 cities: [],
376 places: [],
377 bookings: [],
378 pastBookings: [],
379 showReturnDate: false,
380 showPopup: false,
381 issueDescription: '',
382 userId:'',
383 wishlisted:false,
384 unreadNotifications: 0,
385 notifications: [],
386 notificationsVisible: false,
387 },
388 computed: {
389 selectedFlights() {
390 return this.flights.filter(flight => flight.selected);
391 }
392 },
393
394 methods: {
395 toggleNotifications() {
396 this.notificationsVisible = !this.notificationsVisible;
397 },
398 fetchNotifications() {
399 console.log('Fetching notifications for userId:', this.userId);
400 axios.get(`/api/notifications/${this.userId}`)
401 .then(response => {
402 this.notifications = response.data;
403 this.unreadNotifications = this.notifications.filter(n => !n.read).length;
404 console.log(response.data);
405 })
406 .catch(error => {
407 console.error('Error fetching notifications:', error);
408 });
409 },
410 async searchFlights() {
411 this.isContainerVisible = !this.isContainerVisible;
412 if (this.departureCity && this.destination && this.departureDate) {
413 try {
414 const response = await axios.get('/api/flights/flight-search', {
415 params: {
416 departureCity: this.departureCity,
417 destination: this.destination,
418 departureDate: this.departureDate,
419 returnDate: this.showReturnDate ? this.returnDate : null
420 }
421 });
422
423 if (response.data && response.data.length > 0) {
424 this.flights = response.data;
425 } else {
426 this.flights = [];
427 alert("No flights found for the given criteria.");
428 }
429 } catch (error) {
430 console.error("Error fetching flights", error);
431 }
432 } else {
433 alert("Please select departure city, destination, and date.");
434 this.flights = [];
435 }
436 },
437 bookFlights() {
438 if (!this.selectedFlights.length) {
439 alert("Please select at least one flight.");
440 return;
441 }
442
443 const flight = this.selectedFlights[0];
444 console.log(flight);
445 const totalCost = flight.price;
446
447 const bookingData = {
448 flightId: flight.flightID,
449 bookingDate: new Date().toISOString().split('T')[0],
450 status: 'PENDING',
451 totalCost: totalCost,
452 userId:this.userId
453 };
454 axios.post('/api/bookings', bookingData)
455 .then(response => {
456 const bookingID = response.data.bookingId;
457 alert("Booked successfully!");
458 window.location.href = `/transaction?amount=${encodeURIComponent(totalCost)}&bookingId=${encodeURIComponent(bookingID)}&flightId=${encodeURIComponent(flight.flightID)}&userId=${encodeURIComponent(this.userId)}`;
459 })
460 .catch(error => {
461 console.error("Error booking flight", error);
462 alert("There was an error creating your booking. Please try again.");
463 });
464 },
465 home() {
466 window.location.href = '/';
467 },
468 goToReports(){
469 window.location.href = '/views'
470 },
471 async toggleWishlist(flight) {
472 flight.wishlisted = !flight.wishlisted;
473 this.$set(this.flights, this.flights.indexOf(flight), flight);
474 const wishlistData = {
475 userId: parseInt(this.userId),
476 targetId: flight.flightID
477 };
478 console.log(wishlistData);
479 if (!flight.wishlisted) {
480 try {
481 await axios.delete('/api/wishlists', { params: wishlistData });
482 console.log("Removed from wishlist");
483 } catch (error) {
484 console.error("Error removing from wishlist:", error);
485 }
486 } else {
487 try {
488 await axios.post('/api/wishlists/add', wishlistData);
489 console.log("Added to wishlist");
490 } catch (error) {
491 console.error("Error adding to wishlist:", error);
492 }
493 }
494 },
495 async goToWishlistPage() {
496 window.location.href = `/wishlists?userId=${encodeURIComponent(this.userId)}`;
497 },
498 showReportPopup() {
499 this.showPopup = true;
500 },
501 closePopup() {
502 this.showPopup = false;
503 },
504 submitIssue() {
505 if (this.issueDescription.trim()) {
506 const reviewData = {
507 userId: this.userId,
508 subject: "Issue Report",
509 description: this.issueDescription
510 };
511
512 axios.post('/api/support-tickets', reviewData)
513 .then(response => {
514 alert("Issue reported successfully!");
515 this.closePopup();
516 })
517 .catch(error => {
518 console.error("Error submitting issue:", error);
519 alert("There was an error submitting your issue. Please try again.");
520 });
521 } else {
522 alert("Please enter a description for the issue.");
523 }
524 },
525 async fetchFlights() {
526 try {
527 const response = await axios.get('/api/destinations');
528 this.cities = response.data.map(departureCity => departureCity.name);
529 this.places = response.data.map(destination => destination.name);
530 } catch (error) {
531 console.error("Error fetching Destinations", error);
532 }
533 },
534 goToReviews(){
535 window.location.href = `/reviews?userId=${encodeURIComponent(this.userId)}`;
536 }
537 },
538 mounted() {
539 this.fetchFlights();
540 const params = new URLSearchParams(window.location.search);
541 this.userId = params.get("userId");
542 axios.get('api/flights')
543 .then(response => {
544 this.flights = response.data;
545 })
546 .catch(error => {
547 console.error("Error fetching flights", error);
548 });
549 this.fetchNotifications();
550 }
551 });
552</script>
553
554</body>
555</html>
Note: See TracBrowser for help on using the repository browser.