source: src/main/resources/static/FlightSearch.html@ 9868304

Last change on this file since 9868304 was 9868304, checked in by ste08 <sjovanoska@…>, 4 months ago

Frontend + some backend changes

  • Property mode set to 100644
File size: 14.4 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 <style>
13 body {
14 background: url('images/flight.jpg') no-repeat center center fixed;
15 background-size: cover;
16 display: flex;
17 flex-direction: column;
18 align-items: center;
19 justify-content: center;
20 height: 100vh;
21 margin: 0;
22 font-family: Arial, sans-serif;
23 }
24
25 .header {
26 position: absolute;
27 top: 0;
28 left: 0;
29 display: flex;
30 align-items: center;
31 background-color: rebeccapurple;
32 padding: 10px;
33 width: 100%;
34 z-index: 10;
35 }
36 .header img {
37 width: 40px;
38 height: 40px;
39 margin-right: 20px;
40 }
41
42 .header h1 {
43 color: white;
44 margin: 0;
45 font-size: 15px;
46 padding-right: 50px;
47 }
48
49 .header button {
50 background-color: transparent;
51 border: none;
52 color: white;
53 font-size: 14px;
54 }
55
56 .header button:hover {
57 background-color: transparent;
58 cursor: pointer;
59 }
60
61 .split {
62 height: 100%;
63 width: 50%;
64 position: fixed;
65 z-index: 1;
66 top: 0;
67 overflow-x: hidden;
68 padding-top: 20px;
69 }
70
71 .left {
72 left: 0;
73 padding:50px;
74 }
75
76 .right {
77 right: 0;
78 padding:10px;
79 }
80 select {
81 -webkit-appearance: none;
82 -moz-appearance: none;
83 appearance: none;
84 border: 0;
85 outline: 0;
86 font: inherit;
87 width: 20em;
88 height: 3em;
89 padding: 0 4em 0 1em;
90 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);
91 color: white;
92 border-radius: 0.25em;
93 box-shadow: 0 0 1em 0 rgba(0, 0, 0, 0.2);
94 cursor: pointer;
95 }
96 select option {
97 color: inherit;
98 background-color: #320a28;
99 }
100 select:focus {
101 outline: none;
102 }
103 select::-ms-expand {
104 display: none;
105 }
106
107 .search-form-container h2 {
108 color: white;
109 padding-top: 110px;
110 }
111
112 .search-form {
113 padding: 10px;
114 color:white;
115 font-family: Cambria;
116 }
117 .search {
118 background-color: rebeccapurple;
119 color: white;
120 width: 20em;
121 height: 3em;
122 }
123
124 .book {
125 background-color: darkblue;
126 color: white;
127 width: 20em;
128 height: 3em;
129 }
130
131 .search:hover {
132 background-color: mediumpurple;
133 }
134
135 .flights-list-container {
136 color: white;
137 }
138
139 .flights-list-container h2{
140 padding-top: 110px;
141 }
142 .flights-list .flight-item {
143 margin-bottom: 15px;
144 }
145
146 .flights-list .flight-item span {
147 margin-right: 15px;
148 }
149 .flight-item {
150 display: flex;
151 align-items: center;
152 justify-content: space-between;
153 padding: 15px;
154 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);
155 border-radius: 8px;
156 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
157 transition: transform 0.3s ease, background-color 0.3s ease;
158 width: 70%;
159 height: 15px;
160 }
161
162 .popup-overlay {
163 position: fixed;
164 top: 0;
165 left: 0;
166 width: 100%;
167 height: 100%;
168 background: rgba(0, 0, 0, 0.7);
169 display: flex;
170 align-items: center;
171 justify-content: center;
172 }
173
174 .popup {
175 background-color: white;
176 padding: 20px;
177 width: 300px;
178 border-radius: 10px;
179 }
180
181 .popup button {
182 margin-top: 10px;
183 background-color: rebeccapurple;
184 color: white;
185 border: none;
186 padding: 5px;
187 cursor: pointer;
188 }
189
190 .popup button:hover {
191 background-color: mediumpurple;
192 }
193
194 .calendar {
195 background-color: transparent;
196 border-radius: 2px;
197 border-color: white;
198 width: 18.6em;
199 height: 2.5em;
200 color:white;
201 }
202 </style>
203</head>
204<body>
205
206<div id="app" class="flight-search">
207 <header class="header">
208 <button @click="home"><img src="/images/home.png" alt="Home Icon"></button>
209 <button @click="showReportPopup">Report Issue</button>
210 <button @click="goToWishlistPage">🤍</button>
211 <button @click="home">Log Out</button>
212 </header>
213
214 <div class="split left">
215 <div class="search-form-container">
216 <h2>Search flights</h2>
217
218 <div class="search-form">
219 <label for="departure-city">Departure From</label>
220 <br>
221 <select v-model="departureCity" id="departure-city">
222 <option value="" disabled selected>Select a departure city</option>
223 <option v-for="city in cities" :key="city" :value="city">{{ city }}</option>
224 </select>
225
226 <label for="destination">Destination</label>
227 <br>
228 <select v-model="destination" id="destination">
229 <option value="" disabled selected>Select a destination</option>
230 <option v-for="place in places" :key="place" :value="place">{{ place }}</option>
231 </select>
232
233 <label for="departure-date">Departure Date</label>
234 <br>
235 <input class="calendar" type="date" v-model="departureDate" id="departureDate" />
236 <br>
237 <div class="toggle-wrapper">
238 <label for="return-date-toggle">Include Return Date<input type="checkbox" v-model="showReturnDate" id="return-date-toggle" /></label>
239 </div>
240 <br>
241 <div v-if="showReturnDate">
242 <label for="return-date">Return Date</label>
243 <input class="calendar" type="date" v-model="returnDate" id="returnDate" />
244 </div>
245 <br>
246 <button class="search" @click="searchFlights">Search Flights</button>
247 </div>
248 </div>
249 </div>
250 <div class="split right">
251 <div class="flights-list-container" v-show="isContainerVisible" v-if="flights.length">
252 <h2>Available Flights</h2>
253 <div class="flights-list">
254 <div class="flight-item" v-for="flight in flights" :key="flight.flightId">
255 <input type="checkbox" v-model="flight.selected" />
256 <span>{{ flight.departureTime }} | {{ flight.arrivalTime }} | ${{ flight.price }} | {{ flight.availableSeats }}</span>
257
258 <span class="wishlist-heart" @click="toggleWishlist(flight)">
259 {{ flight.wishlisted ? '❤️' : '🤍' }}
260 </span>
261 </div>
262 </div>
263
264 <button v-if="selectedFlights.length" @click="bookFlights" class="book">
265 Book
266 </button>
267 </div>
268 </div>
269
270 <div v-if="showPopup" class="popup-overlay">
271 <div class="popup">
272 <h3>Report an Issue</h3>
273 <textarea v-model="issueDescription" placeholder="Describe the issue here..." rows="5"></textarea>
274 <div class="popup-actions">
275 <button @click="submitIssue" class="submit-btn">Submit</button>
276 <button @click="closePopup" class="cancel-btn">Cancel</button>
277 </div>
278 </div>
279 </div>
280</div>
281
282<script>
283 new Vue({
284 el: '#app',
285 data: {
286 isContainerVisible:false,
287 departureCity: '',
288 destination: '',
289 departureDate: '',
290 returnDate: '',
291 flights: [],
292 cities: [],
293 places: [],
294 bookings: [],
295 showReturnDate: false,
296 showPopup: false,
297 issueDescription: ""
298 },
299 computed: {
300 selectedFlights() {
301 return this.flights.filter(flight => flight.selected);
302 }
303 },
304 methods: {
305 async searchFlights() {
306 this.isContainerVisible = !this.isContainerVisible;
307 if (this.departureCity && this.destination && this.departureDate) {
308 try {
309 const response = await axios.get('/api/flights/flight-search', {
310 params: {
311 departureCity: this.departureCity,
312 destination: this.destination,
313 departureDate: this.departureDate,
314 returnDate: this.showReturnDate ? this.returnDate : null
315 }
316 });
317
318 if (response.data && response.data.length > 0) {
319 this.flights = response.data;
320 } else {
321 this.flights = [];
322 alert("No flights found for the given criteria.");
323 }
324 } catch (error) {
325 console.error("Error fetching flights", error);
326 }
327 } else {
328 alert("Please select departure city, destination, and date.");
329 this.flights = [];
330 }
331 },
332 bookFlights() {
333 if (!this.selectedFlights.length) {
334 alert("Please select at least one flight.");
335 return;
336 }
337
338 const flight = this.selectedFlights[0];
339 const totalCost = flight.price;
340 const bookingData = {
341 flightId: flight.flightId,
342 bookingDate: new Date().toISOString().split('T')[0],
343 status: 'PENDING',
344 totalCost: totalCost
345 };
346
347 axios.post('/api/bookings', bookingData)
348 .then(response => {
349 const bookingID = response.data.bookingID;
350 alert("Booked successfully!");
351 window.location.href = `/transaction?amount=${encodeURIComponent(totalCost)}&bookingId=${encodeURIComponent(bookingID)}&flightId=${encodeURIComponent(flight.flightId)}`;
352 })
353 .catch(error => {
354 console.error("Error booking flight", error);
355 alert("There was an error creating your booking. Please try again.");
356 });
357 },
358 home() {
359 window.location.href = '/';
360 },
361 async toggleWishlist(flight) {
362 flight.wishlisted = !flight.wishlisted;
363 const wishlistData = {
364 targetId: flight.flightId,
365 wishlisted: flight.wishlisted
366 };
367 try {
368 await axios.post('/api/wishlists', wishlistData);
369 } catch (error) {
370 console.error("Error updating wishlist:", error);
371 }
372 },
373 goToWishlistPage() {
374 window.location.href = '/api/wishlists';
375 },
376 showReportPopup() {
377 this.showPopup = true;
378 },
379 closePopup() {
380 this.showPopup = false;
381 },
382 submitIssue() {
383 if (this.issueDescription.trim()) {
384 const reviewData = {
385 userID: 1,
386 subject: "Issue Report",
387 description: this.issueDescription
388 };
389
390 axios.post('/api/support-tickets', reviewData)
391 .then(response => {
392 alert("Issue reported successfully!");
393 this.closePopup();
394 })
395 .catch(error => {
396 console.error("Error submitting issue:", error);
397 alert("There was an error submitting your issue. Please try again.");
398 });
399 } else {
400 alert("Please enter a description for the issue.");
401 }
402 },
403 async fetchFlights() {
404 try {
405 const response = await axios.get('/api/destinations');
406 this.cities = response.data.map(departureCity => departureCity.name);
407 this.places = response.data.map(destination => destination.name);
408 } catch (error) {
409 console.error("Error fetching Destinations", error);
410 }
411 }
412 },
413 mounted() {
414 this.fetchFlights();
415 axios.get('api/flights')
416 .then(response => {
417 this.flights = response.data;
418 })
419 .catch(error => {
420 console.error("Error fetching flights", error);
421 });
422 }
423 });
424</script>
425
426</body>
427</html>
Note: See TracBrowser for help on using the repository browser.