From 241adb4d549b5df9cbde87414631d411536b385d Mon Sep 17 00:00:00 2001 From: "Cross, Liam (UG - Comp Sci & Elec Eng)" <lc01383@surrey.ac.uk> Date: Sat, 13 Apr 2024 16:31:52 +0000 Subject: [PATCH] Add flight list page and add seats to flight data page --- .../Dashboard/FlightCard/FlightCard.tsx | 7 +- client/src/components/Flight/Flight.scss | 17 +++- client/src/components/Flight/Flight.tsx | 88 ++++++++++++++++--- .../src/components/FlightList/FlightList.scss | 16 ++++ .../src/components/FlightList/FlightList.tsx | 31 +++++++ client/src/components/Header/Header.tsx | 1 + client/src/main.tsx | 7 ++ .../services/Dashboard/CustomerDashboard.ts | 10 ++- client/src/services/Flight/Flight.ts | 18 +++- client/src/services/FlightList/FlightList.ts | 11 +++ 10 files changed, 187 insertions(+), 19 deletions(-) create mode 100644 client/src/components/FlightList/FlightList.scss create mode 100644 client/src/components/FlightList/FlightList.tsx create mode 100644 client/src/services/FlightList/FlightList.ts diff --git a/client/src/components/Dashboard/FlightCard/FlightCard.tsx b/client/src/components/Dashboard/FlightCard/FlightCard.tsx index 5de50b7..1d19058 100644 --- a/client/src/components/Dashboard/FlightCard/FlightCard.tsx +++ b/client/src/components/Dashboard/FlightCard/FlightCard.tsx @@ -5,16 +5,19 @@ import './FlightCard.scss'; interface IFlightCard { flight: IFlight; + extraInfo?: boolean; } -function FlightCard({ flight }: IFlightCard) { +function FlightCard({ flight, extraInfo }: IFlightCard) { return ( <> <div className='flight-card'> <span>{flight.id}</span> + {extraInfo && <span>Departure Time: {new Date(flight.departureTime).toLocaleString()}</span>} + {extraInfo && <span>Arrival Time: {new Date(flight.arrivalTime).toLocaleString()}</span>} <span className='flight-path'>{airportCode(flight.origin)} - {airportCode(flight.destination)}</span> <span>{flight.origin} - {flight.destination}</span> - <Link to={'/flights/' + flight.id}>View Flight</Link> + <Link to={'/flight/' + flight.id}>View Flight</Link> </div> </> ); diff --git a/client/src/components/Flight/Flight.scss b/client/src/components/Flight/Flight.scss index 6f20f8b..800b625 100644 --- a/client/src/components/Flight/Flight.scss +++ b/client/src/components/Flight/Flight.scss @@ -7,8 +7,10 @@ } .flight-data-card { - width: 25vw; + width: 30vw; min-width: 350px; + max-height: 100%; + overflow-y: scroll; } .flight-data-title { @@ -24,3 +26,16 @@ .flight-data-value { font-size: 1.1rem; } + +.seat-lists { + display: flex; + justify-content: space-around; +} + +.seat-available { + color: green; +} + +.seat-unavailable { + color: red; +} diff --git a/client/src/components/Flight/Flight.tsx b/client/src/components/Flight/Flight.tsx index 6c49021..ea6ca3b 100644 --- a/client/src/components/Flight/Flight.tsx +++ b/client/src/components/Flight/Flight.tsx @@ -1,9 +1,29 @@ import { useLoaderData } from 'react-router-dom'; -import { IFlight } from '../../services/Dashboard/CustomerDashboard'; +import { IFlightData } from '../../services/Flight/Flight'; +import { ISeat } from '../../services/Dashboard/CustomerDashboard'; import './Flight.scss'; function Flight() { - const data = useLoaderData() as IFlight; + const data = useLoaderData() as IFlightData; + const flight = data.flight; + + const calculateRows = (seats: ISeat[]) => { + let curr = 0; + let counter = 0; + seats.forEach((seat) => { + const num = +seat.seatNumber.match(/^(\d+)([A-Z]+)$/)![1]; + if (curr !== num) { + counter++; + curr = num; + } + }); + return counter; + }; + + const businessSeats = data.seats.$values.filter((seat) => seat.classType === 0); + const businessRows = calculateRows(businessSeats); + const economySeats = data.seats.$values.filter((seat) => seat.classType === 1); + const economyRows = calculateRows(economySeats); return ( <> @@ -13,47 +33,93 @@ function Flight() { <div className='flight-data-item'> <span className='flight-data-label'>ID: </span> - <span className='flight-data-value'>{data.id}</span> + <span className='flight-data-value'>{flight.id}</span> </div> <div className='flight-data-item'> <span className='flight-data-label'>Origin: </span> - <span className='flight-data-value'>{data.origin}</span> + <span className='flight-data-value'>{flight.origin}</span> </div> <div className='flight-data-item'> <span className='flight-data-label'>Destination: </span> - <span className='flight-data-value'>{data.destination}</span> + <span className='flight-data-value'>{flight.destination}</span> </div> <div className='flight-data-item'> <span className='flight-data-label'>Departure Time: </span> - <span className='flight-data-value'>{new Date(data.departureTime).toLocaleString()}</span> + <span className='flight-data-value'>{new Date(flight.departureTime).toLocaleString()}</span> </div> <div className='flight-data-item'> <span className='flight-data-label'>Arrival Time: </span> - <span className='flight-data-value'>{new Date(data.arrivalTime).toLocaleString()}</span> + <span className='flight-data-value'>{new Date(flight.arrivalTime).toLocaleString()}</span> </div> <div className='flight-data-item'> <span className='flight-data-label'>Economy Capacity: </span> - <span className='flight-data-value'>{data.economyCapacity}</span> + <span className='flight-data-value'>{flight.economyCapacity}</span> </div> <div className='flight-data-item'> <span className='flight-data-label'>Business Capacity: </span> - <span className='flight-data-value'>{data.businessCapacity}</span> + <span className='flight-data-value'>{flight.businessCapacity}</span> </div> <div className='flight-data-item'> <span className='flight-data-label'>Economy Price: </span> - <span className='flight-data-value'>{data.economyPrice}</span> + <span className='flight-data-value'>{flight.economyPrice}</span> </div> <div className='flight-data-item'> <span className='flight-data-label'>Business Price: </span> - <span className='flight-data-value'>{data.businessPrice}</span> + <span className='flight-data-value'>{flight.businessPrice}</span> + </div> + + <div className='flight-data-item seat-lists'> + <div className='flight-data-item'> + <span className='flight-data-label'>Business Seats:</span> + <table className='flight-data-value'> + <tbody> + {[...Array(businessRows)].map((_, rowIndex) => ( + <tr key={rowIndex}> + {[...Array(4)].map((_, cellIndex) => { + const seatNumber = `${rowIndex + 1}${String.fromCharCode(65 + cellIndex)}`; + const seat = businessSeats.find(seat => seat.seatNumber === seatNumber); + const isAvailable = seat ? seat.isAvailable : false; + return ( + <td key={cellIndex} className={isAvailable ? 'seat-available' : 'seat-unavailable'}> + {seatNumber} + </td> + ); + })} + </tr> + ))} + </tbody> + </table> + </div> + + <div className='flight-data-item'> + <span className='flight-data-label'>Economy Seats:</span> + <table className='flight-data-value'> + <tbody> + {[...Array(economyRows)].map((_, rowIndex) => ( + <tr key={rowIndex}> + {[...Array(6)].map((_, cellIndex) => { + const seatNumber = `${rowIndex + businessRows + 1}${String.fromCharCode(65 + cellIndex)}`; + const seat = economySeats.find(seat => seat.seatNumber === seatNumber); + const isAvailable = seat ? seat.isAvailable : false; + return ( + <td key={cellIndex} className={isAvailable ? 'seat-available' : 'seat-unavailable'}> + {seatNumber} + </td> + ); + })} + </tr> + ))} + </tbody> + </table> + </div> </div> </div> </div> diff --git a/client/src/components/FlightList/FlightList.scss b/client/src/components/FlightList/FlightList.scss new file mode 100644 index 0000000..90206bc --- /dev/null +++ b/client/src/components/FlightList/FlightList.scss @@ -0,0 +1,16 @@ +.full-flight-list { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; +} + +.full-flight-list-card { + display: flex; + align-items: center; + width: 20vw; + min-width: 350px; + max-height: 100%; + overflow-y: scroll; +} diff --git a/client/src/components/FlightList/FlightList.tsx b/client/src/components/FlightList/FlightList.tsx new file mode 100644 index 0000000..819bfe3 --- /dev/null +++ b/client/src/components/FlightList/FlightList.tsx @@ -0,0 +1,31 @@ +import { useLoaderData } from 'react-router-dom'; +import { IFlight } from '../../services/Dashboard/CustomerDashboard'; +import FlightCard from '../Dashboard/FlightCard/FlightCard'; +import './FlightList.scss'; + +interface IFlightList { + $values: IFlight[]; +} + +function FlightList() { + const data = useLoaderData() as IFlightList | null; + const flights = data?.$values ?? []; + + return ( + <> + <div className='full-flight-list'> + <div className='card full-flight-list-card'> + { + flights.map((flight) => { + return ( + <FlightCard flight={flight} extraInfo={true} /> + ); + }) + } + </div> + </div> + </> + ); +} + +export default FlightList; diff --git a/client/src/components/Header/Header.tsx b/client/src/components/Header/Header.tsx index bbeca55..3fc4d51 100644 --- a/client/src/components/Header/Header.tsx +++ b/client/src/components/Header/Header.tsx @@ -27,6 +27,7 @@ function Header() { <div> <NavLink to={userToDashboard(user)} className={activeClass} >Dashboard</NavLink> {user?.type === 0 && <NavLink to={'booking/query'} className={activeClass}>Book a Flight</NavLink>} + {user?.type === 1 && <NavLink to={'flights'} className={activeClass}>Flight List</NavLink>} <NavLink to={'logout'} className={activeClass}>Logout</NavLink> </div> : <div> diff --git a/client/src/main.tsx b/client/src/main.tsx index 194da1f..9904a9e 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -9,6 +9,7 @@ import ProtectedRoute from './components/ProtectedRoute/ProtectedRoute.tsx'; import InverseProtectedRoute from './components/ProtectedRoute/InverseProtectedRoute.tsx'; import Dashboard from './components/Dashboard/Dashboard.tsx'; import Flight from './components/Flight/Flight.tsx'; +import FlightList from './components/FlightList/FlightList.tsx'; import BookingQuery from './components/BookingQuery/BookingQuery.tsx'; import BookingList from './components/BookingList/BookingList.tsx'; import { AuthoriseUser } from './services/Authorise/Authorise.ts'; @@ -16,6 +17,7 @@ import { LogoutUser } from './services/Logout/Logout.ts'; import { GetCustomerDashboardData } from './services/Dashboard/CustomerDashboard.ts'; import { GetAirlineDashboardData } from './services/Dashboard/AirlineDashboard.ts'; import { GetFlightData } from './services/Flight/Flight.ts'; +import { GetFlightList } from './services/FlightList/FlightList.ts'; import { GetBookingList } from './services/BookingList/BookingList.ts'; import './index.scss'; @@ -61,6 +63,11 @@ const router = createBrowserRouter([ loader: GetFlightData, element: <Flight></Flight> }, + { + path: 'flights', + loader: GetFlightList, + element: <FlightList></FlightList> + }, { path: 'booking/query', element: <BookingQuery></BookingQuery> diff --git a/client/src/services/Dashboard/CustomerDashboard.ts b/client/src/services/Dashboard/CustomerDashboard.ts index 83a17d1..c05a16f 100644 --- a/client/src/services/Dashboard/CustomerDashboard.ts +++ b/client/src/services/Dashboard/CustomerDashboard.ts @@ -1,5 +1,13 @@ -export interface ISeats { +export interface ISeat { + id: number; + classType: number; + seatNumber: string; + isAvailable: boolean; +} +export interface ISeats { + $id: string; + $values: ISeat[]; } export interface IFlight { diff --git a/client/src/services/Flight/Flight.ts b/client/src/services/Flight/Flight.ts index 062e1ae..18ebb53 100644 --- a/client/src/services/Flight/Flight.ts +++ b/client/src/services/Flight/Flight.ts @@ -1,11 +1,21 @@ import { Params } from 'react-router-dom'; import Api from '../../helpers/Api'; -import { IFlight } from '../Dashboard/CustomerDashboard'; +import { IFlight, ISeats } from '../Dashboard/CustomerDashboard'; -export async function GetFlightData({ params }: { params: Params }): Promise<IFlight> { +export interface IFlightData { + flight: IFlight; + seats: ISeats; +} + +export async function GetFlightData({ params }: { params: Params }): Promise<IFlightData> { try { - const result = await Api.get(`Flight/${params.id}`, { withCredentials: true }); - return result.data; + const flight = Api.get(`Flight/${params.id}`, { withCredentials: true }); + const seats = Api.get(`Flight/${params.id}/seats`, { withCredentials: true }); + const [fdata, sdata] = await Promise.all([flight, seats]); + return { + flight: fdata.data, + seats: sdata.data + }; } catch (error ){ throw error; } diff --git a/client/src/services/FlightList/FlightList.ts b/client/src/services/FlightList/FlightList.ts new file mode 100644 index 0000000..38e3a8e --- /dev/null +++ b/client/src/services/FlightList/FlightList.ts @@ -0,0 +1,11 @@ +import { Params } from 'react-router-dom'; +import Api from '../../helpers/Api'; + +export async function GetFlightList({ params }: { params: Params }) { + try { + const result = await Api.get(`Flight?airlineId=${params.id}`, { withCredentials: true }); + return result.data; + } catch (error) { + return null; + } +} -- GitLab