diff --git a/client/src/components/Dashboard/FlightCard/FlightCard.tsx b/client/src/components/Dashboard/FlightCard/FlightCard.tsx index 32125e5141f5dce5a04af7ef30381ba35a912b7f..82bb495822744c53a58aefd096dae521cf3311d4 100644 --- a/client/src/components/Dashboard/FlightCard/FlightCard.tsx +++ b/client/src/components/Dashboard/FlightCard/FlightCard.tsx @@ -1,13 +1,7 @@ import { Link } from 'react-router-dom'; +import { IFlight } from '../../../services/Dashboard/CustomerDashboard'; import './FlightCard.scss'; -interface IFlight { - id: number; - flightNumber: string; - flightPath: string; - flightPathFull: string; -} - interface IFlightCard { flight: IFlight; } @@ -16,9 +10,9 @@ function FlightCard({ flight }: IFlightCard) { return ( <> <div className='flight-card'> - <span>{flight.flightNumber}</span> - <span className='flight-path'>{flight.flightPath}</span> - <span>{flight.flightPathFull}</span> + <span>{flight.id}</span> + {/* <span className='flight-path'>{flight.origin} - {flight.destination}</span> */} + <span>{flight.origin} - {flight.destination}</span> <Link to={'/flights/' + flight.id}>View Flight</Link> </div> </> diff --git a/client/src/components/Flight/Flight.scss b/client/src/components/Flight/Flight.scss new file mode 100644 index 0000000000000000000000000000000000000000..6f20f8b9045fb3ec74536c5aebe04c5afa75b453 --- /dev/null +++ b/client/src/components/Flight/Flight.scss @@ -0,0 +1,26 @@ +.flight { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; +} + +.flight-data-card { + width: 25vw; + min-width: 350px; +} + +.flight-data-title { + font-size: 1.4rem; + align-self: center; +} + +.flight-data-label { + font-weight: bold; + font-size: 1.2rem; +} + +.flight-data-value { + font-size: 1.1rem; +} diff --git a/client/src/components/Flight/Flight.tsx b/client/src/components/Flight/Flight.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6c4902125370b430cc606c946b9c040a415f17f9 --- /dev/null +++ b/client/src/components/Flight/Flight.tsx @@ -0,0 +1,64 @@ +import { useLoaderData } from 'react-router-dom'; +import { IFlight } from '../../services/Dashboard/CustomerDashboard'; +import './Flight.scss'; + +function Flight() { + const data = useLoaderData() as IFlight; + + return ( + <> + <div className='flight'> + <div className='card flight-data-card'> + <span className='flight-data-title'>Flight Details</span> + + <div className='flight-data-item'> + <span className='flight-data-label'>ID: </span> + <span className='flight-data-value'>{data.id}</span> + </div> + + <div className='flight-data-item'> + <span className='flight-data-label'>Origin: </span> + <span className='flight-data-value'>{data.origin}</span> + </div> + + <div className='flight-data-item'> + <span className='flight-data-label'>Destination: </span> + <span className='flight-data-value'>{data.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> + </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> + </div> + + <div className='flight-data-item'> + <span className='flight-data-label'>Economy Capacity: </span> + <span className='flight-data-value'>{data.economyCapacity}</span> + </div> + + <div className='flight-data-item'> + <span className='flight-data-label'>Business Capacity: </span> + <span className='flight-data-value'>{data.businessCapacity}</span> + </div> + + <div className='flight-data-item'> + <span className='flight-data-label'>Economy Price: </span> + <span className='flight-data-value'>{data.economyPrice}</span> + </div> + + <div className='flight-data-item'> + <span className='flight-data-label'>Business Price: </span> + <span className='flight-data-value'>{data.businessPrice}</span> + </div> + </div> + </div> + </> + ); +} + +export default Flight; diff --git a/client/src/components/FlightCreationForm/FlightCreationForm.tsx b/client/src/components/FlightCreationForm/FlightCreationForm.tsx index 5b687492b216900fc8c7d3f5328249c6b577e2a4..9f0762ef5ccb20a2b6b40f9ff0627a69a952fee6 100644 --- a/client/src/components/FlightCreationForm/FlightCreationForm.tsx +++ b/client/src/components/FlightCreationForm/FlightCreationForm.tsx @@ -1,8 +1,11 @@ import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; import { useForm } from 'react-hook-form'; +import { AxiosError } from 'axios'; +import { addFlight } from '../../services/FlightForm/FlightForm'; import './FlightCreationForm.scss'; -interface IFlightCreationForm { +export interface IFlightCreationForm { origin: string; destination: string; departure: string; @@ -14,14 +17,45 @@ interface IFlightCreationForm { } function FlightCreationForm() { + const navigate = useNavigate(); const [error, setError] = useState(''); const { register, handleSubmit } = useForm<IFlightCreationForm>({mode: 'onChange'}); - const onSubmit = (formValue : IFlightCreationForm) => { + const onSubmit = async (formValue : IFlightCreationForm) => { if (!Number.isInteger(formValue.businessCapacity) || !Number.isInteger(formValue.economyCapacity)) { setError('Please enter an integer for the capacity.') return; } + + if (formValue.economyCapacity % 6 !== 0) { + setError('Economy capacity must be a multiple of 6'); + return; + } + + if (formValue.businessCapacity % 4 !== 0) { + setError('Business capacity must be a multiple of 4'); + return; + } + + if (formValue.origin === formValue.destination) { + setError('Destination cannot be the same as the origin'); + return; + } + + setError(''); + + try { + const result = await addFlight(formValue); + navigate(`/flight/${result.data.id}`); + } catch (error) { + const errorMessage = (error as AxiosError).response?.data; + + if (typeof errorMessage == 'string') { + setError(errorMessage); + } else { + setError('An unexpected error has occurred'); + } + } } return ( @@ -55,22 +89,22 @@ function FlightCreationForm() { <div className='form-col'> <div className='form-group'> <label>Economy Class Capacity</label> - <input type='number' placeholder='Enter capacity' {...register('economyCapacity', { required: true })} /> + <input type='number' placeholder='Enter capacity' {...register('economyCapacity', { required: true, valueAsNumber: true })} /> </div> <div className='form-group'> <label>Business Class Capacity</label> - <input type='number' placeholder='Enter capacity' {...register('businessCapacity', { required: true })} /> + <input type='number' placeholder='Enter capacity' {...register('businessCapacity', { required: true, valueAsNumber: true })} /> </div> <div className='form-group'> <label>Economy Class Price</label> - <input type='number' placeholder='Enter price' {...register('economyPrice', { required: true })} /> + <input type='number' placeholder='Enter price' {...register('economyPrice', { required: true, valueAsNumber: true })} /> </div> <div className='form-group'> <label>Business Class Price</label> - <input type='number' placeholder='Enter price' {...register('businessPrice', { required: true })} /> + <input type='number' placeholder='Enter price' {...register('businessPrice', { required: true, valueAsNumber: true })} /> </div> </div> </div> diff --git a/client/src/main.tsx b/client/src/main.tsx index bde443c098c2754e4f5a44f7879221f58517d7ed..194da1fc83edf214ff0b16d18feeef909a70d38f 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -8,12 +8,14 @@ import Logout from './components/Logout/Logout.tsx'; 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 BookingQuery from './components/BookingQuery/BookingQuery.tsx'; import BookingList from './components/BookingList/BookingList.tsx'; import { AuthoriseUser } from './services/Authorise/Authorise.ts'; 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 { GetBookingList } from './services/BookingList/BookingList.ts'; import './index.scss'; @@ -54,6 +56,11 @@ const router = createBrowserRouter([ loader: GetAirlineDashboardData, element: <Dashboard></Dashboard> }, + { + path: 'flight/:id', + loader: GetFlightData, + element: <Flight></Flight> + }, { path: 'booking/query', element: <BookingQuery></BookingQuery> diff --git a/client/src/services/BookingList/BookingList.ts b/client/src/services/BookingList/BookingList.ts index 84487d8967f77ccfe1881e51c8ae5ad0c1cde6b1..5d593cb6fd7e7dc1a50e619fa4a2d450afb92967 100644 --- a/client/src/services/BookingList/BookingList.ts +++ b/client/src/services/BookingList/BookingList.ts @@ -1,12 +1,10 @@ import { getSearchParam } from '../../helpers/SearchParams'; import { IFlight } from '../Dashboard/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'); @@ -16,27 +14,6 @@ export async function GetBookingList({ request }: { request: Request}): Promise< 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' - }, - ] + flights: [] }; -} \ No newline at end of file +} diff --git a/client/src/services/Dashboard/CustomerDashboard.ts b/client/src/services/Dashboard/CustomerDashboard.ts index a9338d5b87f864c8d62fa31d647e7b827874998b..83a17d151e807dd59766cc3b7d2958b5e4191df2 100644 --- a/client/src/services/Dashboard/CustomerDashboard.ts +++ b/client/src/services/Dashboard/CustomerDashboard.ts @@ -1,12 +1,18 @@ +export interface ISeats { + +} + export interface IFlight { id: number; - flightNumber: string; - flightPath: string; - flightPathFull: string; + origin: string; + destination: string; + arrivalTime: string; + departureTime: string; + economyCapacity: number; + businessCapacity: number; economyPrice: number; businessPrice: number; - departureTime: string; - arrivalTime: string; + seats: ISeats } export interface ICustomerDashboardData { @@ -16,72 +22,9 @@ export interface ICustomerDashboardData { } export async function GetCustomerDashboardData(): Promise<ICustomerDashboardData> { - return { type: 'customer', - upcomingFlights: [ - { - id: 4, - flightNumber: '0004', - 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: 5, - flightNumber: '0005', - 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: 6, - flightNumber: '0006', - 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' - } - ], - flightsHistory: [ - { - id: 1, - flightNumber: '0001', - 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: 2, - flightNumber: '0002', - 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: 3, - flightNumber: '0003', - 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' - } - ] + upcomingFlights: [], + flightsHistory: [] } } diff --git a/client/src/services/Flight/Flight.ts b/client/src/services/Flight/Flight.ts new file mode 100644 index 0000000000000000000000000000000000000000..062e1ae90b9e5d35c2269cccb59c8c716feb651a --- /dev/null +++ b/client/src/services/Flight/Flight.ts @@ -0,0 +1,12 @@ +import { Params } from 'react-router-dom'; +import Api from '../../helpers/Api'; +import { IFlight } from '../Dashboard/CustomerDashboard'; + +export async function GetFlightData({ params }: { params: Params }): Promise<IFlight> { + try { + const result = await Api.get(`Flight/${params.id}`, { withCredentials: true }); + return result.data; + } catch (error ){ + throw error; + } +} diff --git a/client/src/services/FlightForm/FlightForm.ts b/client/src/services/FlightForm/FlightForm.ts new file mode 100644 index 0000000000000000000000000000000000000000..c0bd3981dd513986bba2d3f2c07dd8a92e2a577e --- /dev/null +++ b/client/src/services/FlightForm/FlightForm.ts @@ -0,0 +1,17 @@ +import { AxiosResponse } from 'axios'; +import Api from '../../helpers/Api'; +import { IFlightCreationForm } from '../../components/FlightCreationForm/FlightCreationForm'; +import { IFlight } from '../Dashboard/CustomerDashboard'; + +export async function addFlight(form: IFlightCreationForm): Promise<AxiosResponse<IFlight>> { + return Api.post('Flight', { + Origin: form.origin, + Destination: form.destination, + DepartureTime: form.departure, + ArrivalTime: form.arrival, + EconomyCapacity: form.economyCapacity, + BusinessCapacity: form.businessCapacity, + EconomyPrice: form.economyCapacity, + BusinessPrice: form.businessPrice + }, { withCredentials: true }); +}