From 9eb8a2f00c3f76441d5e17402aab9950517c3cdb Mon Sep 17 00:00:00 2001
From: "Cross, Liam (UG - Comp Sci & Elec Eng)" <lc01383@surrey.ac.uk>
Date: Sun, 31 Mar 2024 17:07:29 +0000
Subject: [PATCH] Add Booking Pages

---
 .../Models/ApplicationDbContext .cs           |  1 -
 .../BookingList/BookingCard/BookingCard.scss  | 46 +++++++++++
 .../BookingList/BookingCard/BookingCard.tsx   | 44 ++++++++++
 .../components/BookingList/BookingList.scss   | 21 +++++
 .../components/BookingList/BookingList.tsx    | 36 ++++++++
 .../components/BookingQuery/BookingQuery.scss | 12 +++
 .../components/BookingQuery/BookingQuery.tsx  | 82 +++++++++++++++++++
 client/src/components/Header/Header.tsx       |  2 +-
 client/src/helpers/SearchParams.ts            |  4 +
 client/src/index.scss                         |  7 ++
 client/src/main.tsx                           | 12 +++
 .../src/services/BookingList/BookingList.ts   | 42 ++++++++++
 .../CustomerDashboard/CustomerDashboard.ts    | 40 +++++++--
 13 files changed, 341 insertions(+), 8 deletions(-)
 create mode 100644 client/src/components/BookingList/BookingCard/BookingCard.scss
 create mode 100644 client/src/components/BookingList/BookingCard/BookingCard.tsx
 create mode 100644 client/src/components/BookingList/BookingList.scss
 create mode 100644 client/src/components/BookingList/BookingList.tsx
 create mode 100644 client/src/components/BookingQuery/BookingQuery.scss
 create mode 100644 client/src/components/BookingQuery/BookingQuery.tsx
 create mode 100644 client/src/helpers/SearchParams.ts
 create mode 100644 client/src/services/BookingList/BookingList.ts

diff --git a/UserMicroservice/Models/ApplicationDbContext .cs b/UserMicroservice/Models/ApplicationDbContext .cs
index c22b1e8..d9bf5b8 100644
--- a/UserMicroservice/Models/ApplicationDbContext .cs	
+++ b/UserMicroservice/Models/ApplicationDbContext .cs	
@@ -1,6 +1,5 @@
 using Microsoft.EntityFrameworkCore;
 using static Microsoft.EntityFrameworkCore.DbLoggerCategory;
-using UserMicroservice.Migrations;
 
 namespace UserMicroservice.Models
 {
diff --git a/client/src/components/BookingList/BookingCard/BookingCard.scss b/client/src/components/BookingList/BookingCard/BookingCard.scss
new file mode 100644
index 0000000..a46873e
--- /dev/null
+++ b/client/src/components/BookingList/BookingCard/BookingCard.scss
@@ -0,0 +1,46 @@
+.booking-card {
+  display: flex;
+  flex-direction: column;
+  gap: 1rem;
+  border: 1px solid black;
+  border-radius: 5px;
+  padding: 1rem;
+
+  .price {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    .price-label {
+      display: flex;
+      gap: 0.5rem;
+    }
+
+    button {
+      font-size: 1rem;
+      padding: 6px 12px;
+      border: 1px solid gray;
+      border-radius: 5px;
+      background-color: var(--main);
+      color: white;
+      margin-left: 1rem;
+    }
+  
+    button:hover {
+      filter: brightness(50%);
+      cursor: pointer;
+    }
+  }
+}
+
+.booking-card-details {
+  display: flex;
+  justify-content: space-around;
+  flex-direction: row;
+  gap: 1rem;
+
+  .item {
+    display: flex;
+    flex-direction: column;
+  }
+}
\ No newline at end of file
diff --git a/client/src/components/BookingList/BookingCard/BookingCard.tsx b/client/src/components/BookingList/BookingCard/BookingCard.tsx
new file mode 100644
index 0000000..900011a
--- /dev/null
+++ b/client/src/components/BookingList/BookingCard/BookingCard.tsx
@@ -0,0 +1,44 @@
+import { useLocation } from 'react-router-dom';
+import { IFlight } from '../../../services/CustomerDashboard/CustomerDashboard';
+import './BookingCard.scss';
+
+interface IBookingCard {
+  flight: IFlight
+}
+
+function BookingCard({ flight }: IBookingCard) {
+  const location = useLocation();
+  const isEconomy = location.search.split('seatType=')[1] === 'economy';
+
+  const handleClick = () => {
+    console.log('ready to redirect:', flight.id);
+  };
+
+  return (
+    <>
+      <div className='booking-card'>
+        <div className='booking-card-details'>
+          <div className='item'>
+            <span>Departure Time:</span>
+            <span>{flight.departureTime}</span>
+          </div>
+
+          <div className='item'>
+            <span>Arrival Time:</span>
+            <span>{flight.arrivalTime}</span>
+          </div>
+        </div>
+
+        <div className='price'>
+          <div className='price-label'>
+            <span>{isEconomy ? 'Economy' : 'Business'} Price:</span>
+            <span>£{isEconomy ? flight.economyPrice : flight.businessPrice}</span>
+          </div>
+          <button type='button' onClick={handleClick}>Purchase</button>
+        </div>
+      </div>
+    </>
+  );
+}
+
+export default BookingCard;
\ No newline at end of file
diff --git a/client/src/components/BookingList/BookingList.scss b/client/src/components/BookingList/BookingList.scss
new file mode 100644
index 0000000..3b68214
--- /dev/null
+++ b/client/src/components/BookingList/BookingList.scss
@@ -0,0 +1,21 @@
+.booking-list {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 100%;
+}
+
+.booking-list-card {
+  width: 40vw;
+  min-width: 350px;
+}
+
+.booking-list-title {
+  align-self: center;
+  font-size: 1.25rem;
+}
+
+.booking-bold {
+  font-weight: bold;
+}
\ No newline at end of file
diff --git a/client/src/components/BookingList/BookingList.tsx b/client/src/components/BookingList/BookingList.tsx
new file mode 100644
index 0000000..d87ca26
--- /dev/null
+++ b/client/src/components/BookingList/BookingList.tsx
@@ -0,0 +1,36 @@
+import { useLoaderData, useLocation } from 'react-router-dom';
+import { IBookingList } from '../../services/BookingList/BookingList';
+import BookingCard from './BookingCard/BookingCard';
+import './BookingList.scss';
+
+function BookingList() {
+  const data = useLoaderData() as IBookingList;
+  const location = useLocation();
+  const isEconomy = location.search.split('seatType=')[1] === 'economy';
+  const origin = location.search.split('origin=')[1].split('&')[0];
+  const destination = location.search.split('destination=')[1].split('&')[0];
+  const date = location.search.split('date=')[1].split('&')[0];
+
+  return (
+    <>
+      <div className='booking-list'>
+        <div className='card booking-list-card'>
+          <div className='booking-list-title'>
+            <span>Showing </span>
+            <span className='booking-bold'>{isEconomy ? 'Economy' : 'Business'}</span>
+            <span> Flights for </span>
+            <span className='booking-bold'>{origin} - {destination}</span>
+            <span> on </span>
+            <span className='booking-bold'>{date}</span>
+          </div>
+
+          {data.flights.map((flight) => {
+            return <BookingCard key={flight.id} flight={flight}></BookingCard>
+          })}
+        </div>
+      </div>
+    </>
+  );
+}
+
+export default BookingList;
\ No newline at end of file
diff --git a/client/src/components/BookingQuery/BookingQuery.scss b/client/src/components/BookingQuery/BookingQuery.scss
new file mode 100644
index 0000000..8615421
--- /dev/null
+++ b/client/src/components/BookingQuery/BookingQuery.scss
@@ -0,0 +1,12 @@
+.booking-query {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 100%;
+}
+
+.booking-query-card {
+  width: 20vw;
+  min-width: 350px;
+}
\ No newline at end of file
diff --git a/client/src/components/BookingQuery/BookingQuery.tsx b/client/src/components/BookingQuery/BookingQuery.tsx
new file mode 100644
index 0000000..c78c892
--- /dev/null
+++ b/client/src/components/BookingQuery/BookingQuery.tsx
@@ -0,0 +1,82 @@
+import { useState } from 'react';
+import { useForm } from 'react-hook-form';
+import { useNavigate } from 'react-router-dom';
+import './BookingQuery.scss';
+
+interface IBookingQuery {
+  origin: string;
+  destination: string;
+  date: string;
+  seatType: string;
+}
+
+function BookingQuery() {
+  const navigate = useNavigate();
+  const [error, setError] = useState('');
+  const { register, handleSubmit } = useForm<IBookingQuery>({mode: 'onChange', defaultValues: { origin: '', destination: '', seatType: ''}});
+  const airports: string[] = ['LGW', 'LHR', 'BHX', 'MAN', 'EDI'];
+
+  const onSubmit = (query: IBookingQuery) => {
+    if (query.origin === query.destination) {
+      setError('Destination cannot be the same as origin');
+      return;
+    }
+
+    setError('');
+    navigate(`/booking/list?origin=${query.origin}&destination=${query.destination}&date=${query.date}&seatType=${query.seatType}`);
+  };
+
+  return (
+    <>
+      <div className='booking-query'>
+        <form onSubmit={handleSubmit(onSubmit)}>
+          <div className='card booking-query-card'>
+            <div className='form-group'>
+              <label>Origin:</label>
+              <select {...register('origin', { required: true })}>
+                <option value={''} disabled>Select an airport</option>
+                {airports.map((airport) => {
+                  return <option key={airport} value={airport}>{airport}</option>
+                })}
+              </select>
+            </div>
+
+            <div className='form-group'>
+              <label>Destination:</label>
+              <select {...register('destination', { required: true })}>
+                <option value='' disabled>Select an airport</option>
+                {airports.map((airport) => {
+                  return <option key={airport} value={airport}>{airport}</option>
+                })}
+              </select>
+            </div>
+
+            <div className='form-group'>
+              <label>Departure Date:</label>
+              <input type='date' min={new Date().toISOString().split('T')[0]} {...register('date', { required: true })}></input>
+            </div>
+
+            <div className='form-group'>
+              <label>Seat Type:</label>
+              <select {...register('seatType', { required: true })}>
+                <option value='' disabled>Select Seat Type</option>
+                <option value='economy'>Economy</option>
+                <option value='business'>Business</option>
+              </select>
+            </div>
+
+            <div className='form-group'>
+              <button type='submit'>Submit</button>
+            </div>
+
+            <div className='form-group'>
+              {error && <span>{error}</span>}
+            </div>
+          </div>
+        </form>
+      </div>
+    </>
+  );
+}
+
+export default BookingQuery;
\ No newline at end of file
diff --git a/client/src/components/Header/Header.tsx b/client/src/components/Header/Header.tsx
index 7b477ff..14ad345 100644
--- a/client/src/components/Header/Header.tsx
+++ b/client/src/components/Header/Header.tsx
@@ -10,7 +10,7 @@ function Header() {
 
           <nav className='nav'>
             <Link to={'/'} className='nav-item'>Home</Link>
-            <Link to={'test'} className='nav-item'>Test Nav</Link>
+            <Link to={'booking/query'} className='nav-item'>Book a Flight</Link>
           </nav>
         </div>
       </div>
diff --git a/client/src/helpers/SearchParams.ts b/client/src/helpers/SearchParams.ts
new file mode 100644
index 0000000..8e3cc93
--- /dev/null
+++ b/client/src/helpers/SearchParams.ts
@@ -0,0 +1,4 @@
+export function getSearchParam(requestURL: string, param: string): string {
+  const url = new URL(requestURL);
+  return url.searchParams.get(param) ?? '';
+}
\ No newline at end of file
diff --git a/client/src/index.scss b/client/src/index.scss
index 7c138bb..384cc90 100644
--- a/client/src/index.scss
+++ b/client/src/index.scss
@@ -49,6 +49,13 @@ body {
     border-radius: 5px;
   }
 
+  select {
+    font-size: 1rem;
+    padding: 6px 12px;
+    border: 1px solid gray;
+    border-radius: 5px;
+  }
+
   button {
     font-size: 1rem;
     padding: 6px 12px;
diff --git a/client/src/main.tsx b/client/src/main.tsx
index ac1959e..758311d 100644
--- a/client/src/main.tsx
+++ b/client/src/main.tsx
@@ -5,8 +5,11 @@ import App from './App.tsx';
 import Login from './components/Login/Login.tsx';
 import Register from './components/Register/Register.tsx';
 import CustomerDashboard from './components/CustomerDashboard/CustomerDashboard.tsx';
+import BookingQuery from './components/BookingQuery/BookingQuery.tsx';
+import BookingList from './components/BookingList/BookingList.tsx';
 import { GetCustomerDashboardData } from './services/CustomerDashboard/CustomerDashboard.ts';
 import './index.scss';
+import { GetBookingList } from './services/BookingList/BookingList.ts';
 
 const router = createBrowserRouter([
   {
@@ -25,6 +28,15 @@ const router = createBrowserRouter([
         path: 'customer-dashboard',
         loader: GetCustomerDashboardData,
         element: <CustomerDashboard></CustomerDashboard>
+      },
+      {
+        path: 'booking/query',
+        element: <BookingQuery></BookingQuery>
+      },
+      {
+        path: 'booking/list',
+        loader: GetBookingList,
+        element: <BookingList></BookingList>
       }
     ]
   }
diff --git a/client/src/services/BookingList/BookingList.ts b/client/src/services/BookingList/BookingList.ts
new file mode 100644
index 0000000..985a359
--- /dev/null
+++ b/client/src/services/BookingList/BookingList.ts
@@ -0,0 +1,42 @@
+import { getSearchParam } from '../../helpers/SearchParams';
+import { IFlight } from '../CustomerDashboard/CustomerDashboard';
+
+
+export interface IBookingList {
+  flights: IFlight[]
+}
+
+
+export async function GetBookingList({ request }: { request: Request}): Promise<IBookingList> {
+  const origin = getSearchParam(request.url, 'origin');
+  const destination = getSearchParam(request.url, 'destination');
+  const date = getSearchParam(request.url, 'date');
+  const seatType = getSearchParam(request.url, 'seatType');
+
+  console.log('ready to call API with:', origin, destination, date, seatType);
+
+  return {
+    flights: [
+      {
+        id: 11,
+        flightNumber: '0011',
+        flightPath: 'LTN - MLG',
+        flightPathFull: 'London(LTN) - Spain(MLG)',
+        economyPrice: 50,
+        businessPrice: 100,
+        departureTime: '10/8/2024 11:00:00 AM',
+        arrivalTime: '10/8/2024 13:00:00 PM'
+      },
+      {
+        id: 12,
+        flightNumber: '0012',
+        flightPath: 'LTN - MLG',
+        flightPathFull: 'London(LTN) - Spain(MLG)',
+        economyPrice: 50,
+        businessPrice: 100,
+        departureTime: '11/8/2024 11:00:00 AM',
+        arrivalTime: '11/8/2024 13:00:00 PM'
+      },
+    ]
+  };
+}
\ No newline at end of file
diff --git a/client/src/services/CustomerDashboard/CustomerDashboard.ts b/client/src/services/CustomerDashboard/CustomerDashboard.ts
index 81c852e..afcd16f 100644
--- a/client/src/services/CustomerDashboard/CustomerDashboard.ts
+++ b/client/src/services/CustomerDashboard/CustomerDashboard.ts
@@ -4,6 +4,10 @@ export interface IFlight {
   flightNumber: string;
   flightPath: string;
   flightPathFull: string;
+  economyPrice: number;
+  businessPrice: number;
+  departureTime: string;
+  arrivalTime: string;
 }
 
 export interface ICustomerDashboardData {
@@ -22,19 +26,31 @@ export async function GetCustomerDashboardData(): Promise<ICustomerDashboardData
         id: 4,
         flightNumber: '0004',
         flightPath: 'LTN - MLG',
-        flightPathFull: 'London(LTN) - Spain(MLG)'
+        flightPathFull: 'London(LTN) - Spain(MLG)',
+        economyPrice: 50,
+        businessPrice: 100,
+        departureTime: '10/8/2024 11:00:00 AM',
+        arrivalTime: '10/8/2024 13:00:00 PM'
       },
       {
         id: 5,
         flightNumber: '0005',
         flightPath: 'LTN - MLG',
-        flightPathFull: 'London(LTN) - Spain(MLG)'
+        flightPathFull: 'London(LTN) - Spain(MLG)',
+        economyPrice: 50,
+        businessPrice: 100,
+        departureTime: '10/8/2024 11:00:00 AM',
+        arrivalTime: '10/8/2024 13:00:00 PM'
       },
       {
         id: 6,
         flightNumber: '0006',
         flightPath: 'LTN - MLG',
-        flightPathFull: 'London(LTN) - Spain(MLG)'
+        flightPathFull: 'London(LTN) - Spain(MLG)',
+        economyPrice: 50,
+        businessPrice: 100,
+        departureTime: '10/8/2024 11:00:00 AM',
+        arrivalTime: '10/8/2024 13:00:00 PM'
       }
     ],
     flightsHistory: [
@@ -42,19 +58,31 @@ export async function GetCustomerDashboardData(): Promise<ICustomerDashboardData
         id: 1,
         flightNumber: '0001',
         flightPath: 'LTN - MLG',
-        flightPathFull: 'London(LTN) - Spain(MLG)'
+        flightPathFull: 'London(LTN) - Spain(MLG)',
+        economyPrice: 50,
+        businessPrice: 100,
+        departureTime: '10/8/2024 11:00:00 AM',
+        arrivalTime: '10/8/2024 13:00:00 PM'
       },
       {
         id: 2,
         flightNumber: '0002',
         flightPath: 'LTN - MLG',
-        flightPathFull: 'London(LTN) - Spain(MLG)'
+        flightPathFull: 'London(LTN) - Spain(MLG)',
+        economyPrice: 50,
+        businessPrice: 100,
+        departureTime: '10/8/2024 11:00:00 AM',
+        arrivalTime: '10/8/2024 13:00:00 PM'
       },
       {
         id: 3,
         flightNumber: '0003',
         flightPath: 'LTN - MLG',
-        flightPathFull: 'London(LTN) - Spain(MLG)'
+        flightPathFull: 'London(LTN) - Spain(MLG)',
+        economyPrice: 50,
+        businessPrice: 100,
+        departureTime: '10/8/2024 11:00:00 AM',
+        arrivalTime: '10/8/2024 13:00:00 PM'
       }
     ]
   }
-- 
GitLab