From 280b222d9a6103f49fc82eb2afc2a29cfa796fe8 Mon Sep 17 00:00:00 2001 From: Noah Spannbauer Date: Fri, 24 Oct 2025 08:31:13 -0500 Subject: [PATCH] migrating to sqlite --- api/package.json | 1 + api/src/app.module.ts | 4 +- .../interfaces/customJwtPayload.interface.ts | 5 + api/src/log/interceptors/log.interceptor.ts | 42 - api/src/log/log.controller.ts | 15 +- api/src/log/log.dto.ts | 2 +- api/src/log/log.entity.ts | 3 + api/src/log/log.interceptor.ts | 39 + api/src/log/log.service.ts | 2 +- .../pilot/interceptors/pilot.interceptor.ts | 35 - api/src/pilot/pilot.controller.ts | 9 +- api/src/pilot/pilot.interceptor.ts | 33 + api/src/track/track.controller.ts | 2 + api/src/track/track.service.ts | 1 + client/package.json | 7 +- client/src/App.tsx | 18 +- client/src/auth/oidcConfig.ts | 7 +- .../src/components/actionMenu/ActionMenu.tsx | 39 +- client/src/components/flights/Flights.tsx | 41 +- .../flights/FlightsState.interface.ts | 8 + client/src/components/flights/reducer.tsx | 44 + .../src/components/logForm/ILogFormProps.ts | 8 - .../src/components/logForm/ILogFormState.ts | 12 - client/src/components/logForm/LogForm.tsx | 1471 ++++--- .../logForm/LogFormProps.interface.ts | 8 + .../logForm/LogFormState.interface.ts | 13 + client/src/components/logForm/reducer.tsx | 35 +- .../components/logTrackMaps/LogTrackMaps.tsx | 3 +- client/src/components/logTracks/LogTracks.tsx | 209 - client/src/components/logbook/Logbook.tsx | 615 ++- ...bookEntry.ts => LogbookEntry.interface.ts} | 8 +- ...bookState.ts => LogbookState.interface.ts} | 13 +- client/src/components/logbook/columns.tsx | 77 +- client/src/components/logbook/reducer.ts | 57 +- .../logbookCard/LogbookCardProps.interface.ts | 4 +- .../logbookDrawer/LogbookDrawer.tsx | 154 + .../LogbookDrawerProps.interface.ts | 5 + .../LogbookDrawerState.interface.ts | 0 client/src/components/pilotForm/PilotForm.tsx | 476 +-- .../src/components/pilots/Pilot.interface.ts | 6 + client/src/components/pilots/Pilots.tsx | 193 +- client/src/components/siteNav/SiteNav.tsx | 85 +- .../src/components/tracksForm/TracksForm.tsx | 224 ++ .../TracksFormProps.interface.ts} | 2 +- .../TracksFormState.interface.ts} | 2 +- .../{logTracks => tracksForm}/reducer.ts | 6 +- .../context/logbookContext/LogbookContext.tsx | 5 + .../LogbookContextProps.interface.ts | 7 + .../logbookContext/LogbookContextProvider.tsx | 35 + .../LogbookContextProviderProps.interface.ts | 3 + .../LogbookContextState.interface.ts | 11 + client/src/context/logbookContext/reducer.ts | 67 + client/src/enums/screenSize.ts | 7 + client/src/hero.ts | 3 + client/src/hooks/httpClient/UseHttpClient.tsx | 29 - .../logbookContext/UseLogbookContext.tsx | 11 + client/src/hooks/logs/UseLogs.tsx | 25 +- client/src/hooks/pilots/UsePilots.tsx | 21 +- .../hooks/useBreakpoints/UseBreakpoints.tsx | 66 + client/src/hooks/userRole/UseUserRole.tsx | 6 +- client/src/httpClient/httpClient.ts | 4 +- client/src/index.css | 12 - client/src/interfaces/Alert.interface.ts | 2 +- client/src/main.tsx | 41 +- client/src/styles.css | 10 + client/src/tanstack.d.ts | 10 + database/flying.db | Bin 77824 -> 94208 bytes package-lock.json | 3515 ++++++++++++++++- package.json | 5 +- 69 files changed, 6053 insertions(+), 1885 deletions(-) create mode 100644 api/src/interfaces/customJwtPayload.interface.ts delete mode 100644 api/src/log/interceptors/log.interceptor.ts create mode 100644 api/src/log/log.interceptor.ts delete mode 100644 api/src/pilot/interceptors/pilot.interceptor.ts create mode 100644 api/src/pilot/pilot.interceptor.ts create mode 100644 client/src/components/flights/FlightsState.interface.ts create mode 100644 client/src/components/flights/reducer.tsx delete mode 100644 client/src/components/logForm/ILogFormProps.ts delete mode 100644 client/src/components/logForm/ILogFormState.ts create mode 100644 client/src/components/logForm/LogFormProps.interface.ts create mode 100644 client/src/components/logForm/LogFormState.interface.ts delete mode 100644 client/src/components/logTracks/LogTracks.tsx rename client/src/components/logbook/{ILogbookEntry.ts => LogbookEntry.interface.ts} (85%) rename client/src/components/logbook/{ILogbookState.ts => LogbookState.interface.ts} (50%) create mode 100644 client/src/components/logbookDrawer/LogbookDrawer.tsx create mode 100644 client/src/components/logbookDrawer/LogbookDrawerProps.interface.ts create mode 100644 client/src/components/logbookDrawer/LogbookDrawerState.interface.ts create mode 100644 client/src/components/tracksForm/TracksForm.tsx rename client/src/components/{logTracks/LogTracksProps.interface.ts => tracksForm/TracksFormProps.interface.ts} (83%) rename client/src/components/{logTracks/LogTracksState.interface.ts => tracksForm/TracksFormState.interface.ts} (86%) rename client/src/components/{logTracks => tracksForm}/reducer.ts (86%) create mode 100644 client/src/context/logbookContext/LogbookContext.tsx create mode 100644 client/src/context/logbookContext/LogbookContextProps.interface.ts create mode 100644 client/src/context/logbookContext/LogbookContextProvider.tsx create mode 100644 client/src/context/logbookContext/LogbookContextProviderProps.interface.ts create mode 100644 client/src/context/logbookContext/LogbookContextState.interface.ts create mode 100644 client/src/context/logbookContext/reducer.ts create mode 100644 client/src/enums/screenSize.ts create mode 100644 client/src/hero.ts delete mode 100644 client/src/hooks/httpClient/UseHttpClient.tsx create mode 100644 client/src/hooks/logbookContext/UseLogbookContext.tsx create mode 100644 client/src/hooks/useBreakpoints/UseBreakpoints.tsx delete mode 100644 client/src/index.css create mode 100644 client/src/styles.css create mode 100644 client/src/tanstack.d.ts diff --git a/api/package.json b/api/package.json index f25b705..f9a6e7d 100644 --- a/api/package.json +++ b/api/package.json @@ -42,6 +42,7 @@ "dotenv": "^16.6.1", "express-session": "^1.18.2", "jwks-rsa": "^3.2.0", + "jwt-decode": "^4.0.0", "node-gyp": "^11.4.1", "passport": "^0.7.0", "passport-jwt": "^4.0.1", diff --git a/api/src/app.module.ts b/api/src/app.module.ts index b4b5cb1..c5c179a 100644 --- a/api/src/app.module.ts +++ b/api/src/app.module.ts @@ -31,12 +31,12 @@ import { join } from 'path'; load: [configuration] }), // HealthModule, - // LogModule, + LogModule, PilotModule, ServeStaticModule.forRoot({ rootPath: join(__dirname, '../..', 'client', 'dist') }), - // TrackModule, + TrackModule, TypeOrmModule.forRoot(dataSourceOptions), MsGraphModule.registerAsync({ inject: [ConfigService], diff --git a/api/src/interfaces/customJwtPayload.interface.ts b/api/src/interfaces/customJwtPayload.interface.ts new file mode 100644 index 0000000..17b3c82 --- /dev/null +++ b/api/src/interfaces/customJwtPayload.interface.ts @@ -0,0 +1,5 @@ +import { JwtPayload } from "jwt-decode"; + +export interface CustomJwtPayload extends JwtPayload { + roles: string[]; +} \ No newline at end of file diff --git a/api/src/log/interceptors/log.interceptor.ts b/api/src/log/interceptors/log.interceptor.ts deleted file mode 100644 index 5e1de72..0000000 --- a/api/src/log/interceptors/log.interceptor.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common'; -import { Observable, map } from 'rxjs'; -import { LogEntity } from '../log.entity'; - -export class LogInterceptor implements NestInterceptor { - intercept(context: ExecutionContext, handler: CallHandler): Observable { - const req = context.switchToHttp().getRequest(); - const authHeader = req.headers.authorization; - const token = authHeader && authHeader.split(' ')[1]; - - if (!token) { - return handler.handle().pipe( - map((data: LogEntity[]) => { - - if (data.length) { - const logs = data.map((log: LogEntity) => { - return { - id: log.id, - pilot: { - name: log.pilot.name - }, - date: log.date, - aircraftMakeModel: log.aircraftMakeModel, - routeFrom: log.routeFrom, - routeTo: log.routeTo, - durationOfFlight: log.durationOfFlight, - tracks: log.tracks, - notes: log.notes - }; - }); - - return logs; - } else { - return data; - } - }) - ); - } - - return handler.handle().pipe(map((data) => data)); - } -} diff --git a/api/src/log/log.controller.ts b/api/src/log/log.controller.ts index 8f2dbfe..f139cf6 100644 --- a/api/src/log/log.controller.ts +++ b/api/src/log/log.controller.ts @@ -15,11 +15,13 @@ import { LogEntity } from './log.entity'; import { LogService } from './log.service'; import { CustomError } from '../error/customError'; import { AuthGuard } from '@noahspan/noahspan-modules'; -import { LogInterceptor } from './interceptors/log.interceptor'; +import { LogInterceptor } from './log.interceptor'; import { FileService } from '../file/file.service'; import { DeleteResult, InsertResult, UpdateResult } from 'typeorm'; @Controller('logs') +@UseInterceptors(new LogInterceptor()) +// @UseGuards(AuthGuard) export class LogController { constructor( private readonly fileService: FileService, @@ -28,11 +30,11 @@ export class LogController { @Get(':id') - @UseInterceptors(new LogInterceptor()) async find( @Param('id') id: string, ): Promise { try { + console.log(id) return await this.logService.find(id); } catch (error) { const customError = error as CustomError; @@ -42,7 +44,6 @@ export class LogController { } @Get() - @UseInterceptors(new LogInterceptor()) async findAll(): Promise { try { return await this.logService.findAll(); @@ -53,7 +54,7 @@ export class LogController { } } - // @UseGuards(AuthGuard) + @Post() async create(@Body() logDto: LogDto): Promise { try { @@ -65,22 +66,22 @@ export class LogController { } } - // @UseGuards(AuthGuard) @Put(':id') async update( @Param('id') id: string, @Body() logDto: LogDto ): Promise { try { + console.log(id) + console.log(logDto) return await this.logService.update(id, logDto); } catch (error) { const customError = error as CustomError; - + console.log(error) throw new HttpException(customError.message, customError.statusCode); } } - // @UseGuards(AuthGuard) @Delete(':id') async delete( @Param('id') id: string, diff --git a/api/src/log/log.dto.ts b/api/src/log/log.dto.ts index ff3a5fb..561984f 100644 --- a/api/src/log/log.dto.ts +++ b/api/src/log/log.dto.ts @@ -24,5 +24,5 @@ export class LogDto { solo?: number; pilotInCommand?: number; notes?: string; - pilot?: PilotEntity; + tracks?: [] } diff --git a/api/src/log/log.entity.ts b/api/src/log/log.entity.ts index 82e2d97..884645f 100644 --- a/api/src/log/log.entity.ts +++ b/api/src/log/log.entity.ts @@ -7,6 +7,9 @@ export class LogEntity { @PrimaryGeneratedColumn('uuid') id: string + @Column() + pilotId: string; + @Column() date: Date; diff --git a/api/src/log/log.interceptor.ts b/api/src/log/log.interceptor.ts new file mode 100644 index 0000000..f10f6f9 --- /dev/null +++ b/api/src/log/log.interceptor.ts @@ -0,0 +1,39 @@ +import { CallHandler, ExecutionContext, NestInterceptor, UnauthorizedException } from '@nestjs/common'; +import { Observable, map } from 'rxjs'; +import { LogEntity } from './log.entity'; +import { jwtDecode } from 'jwt-decode'; +import { CustomJwtPayload } from 'src/interfaces/customJwtPayload.interface'; + +export class LogInterceptor implements NestInterceptor { + intercept(context: ExecutionContext, handler: CallHandler): Observable { + const req = context.switchToHttp().getRequest(); + const authHeader = req.headers.authorization; + const token = authHeader && authHeader.split(' ')[1]; + const jwtPayload: CustomJwtPayload = jwtDecode(token); + + return handler.handle().pipe( + map((data: LogEntity[]) => { + if (data.length > 0 && jwtPayload.roles.includes('Flying.Read')) { + const logs = data.map((log: LogEntity) => { + return { + id: log.id, + pilot: { + name: log.pilot.name + }, + date: log.date, + aircraftMakeModel: log.aircraftMakeModel, + routeFrom: log.routeFrom, + routeTo: log.routeTo, + durationOfFlight: log.durationOfFlight, + tracks: log.tracks, + }; + }); + + return logs; + } else { + return data; + } + }) + ); + } +} diff --git a/api/src/log/log.service.ts b/api/src/log/log.service.ts index 2638688..2076da2 100644 --- a/api/src/log/log.service.ts +++ b/api/src/log/log.service.ts @@ -18,7 +18,7 @@ export class LogService { async find(id: string): Promise { const logEntity: LogEntity = await this.logRepository.findOne({ where: { id: id }, - relations: ['pilot', 'tracks'] + // relations: ['pilot', 'tracks'] }); return logEntity; diff --git a/api/src/pilot/interceptors/pilot.interceptor.ts b/api/src/pilot/interceptors/pilot.interceptor.ts deleted file mode 100644 index 6089ccb..0000000 --- a/api/src/pilot/interceptors/pilot.interceptor.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common'; -import { Observable, map } from 'rxjs'; - -export class PilotInterceptor implements NestInterceptor { - intercept(context: ExecutionContext, handler: CallHandler): Observable { - const req = context.switchToHttp().getRequest(); - const authHeader = req.headers.authorization; - const token = authHeader && authHeader.split(' ')[1]; - - if (!token) { - return handler.handle().pipe( - map((data) => { - if (data.length) { - const pilots = data.map((pilot) => { - return { - partitionKey: pilot.partitionKey, - rowKey: pilot.rowKey, - id: pilot.id, - name: pilot.name, - certificates: pilot.certificates, - endorsements: pilot.endorsements - }; - }); - - return pilots; - } else { - return data; - } - }) - ); - } - - return handler.handle().pipe(map((data) => data)); - } -} diff --git a/api/src/pilot/pilot.controller.ts b/api/src/pilot/pilot.controller.ts index d7137da..a3182d7 100644 --- a/api/src/pilot/pilot.controller.ts +++ b/api/src/pilot/pilot.controller.ts @@ -14,11 +14,12 @@ import { PilotDto } from './pilot.dto'; import { PilotEntity } from './pilot.entity'; import { PilotService } from './pilot.service'; import { CustomError } from '../error/customError'; -import { PilotInterceptor } from './interceptors/pilot.interceptor'; +import { PilotInterceptor } from './pilot.interceptor'; import { AuthGuard } from '@noahspan/noahspan-modules'; @Controller('pilots') -// @UseInterceptors(new PilotInterceptor()) +@UseInterceptors(new PilotInterceptor()) +@UseGuards(AuthGuard) export class PilotController { constructor(private readonly pilotService: PilotService) {} @@ -34,7 +35,6 @@ export class PilotController { } @Get() - @UseGuards(AuthGuard) async findAll() { try { return await this.pilotService.findAll(); @@ -45,7 +45,6 @@ export class PilotController { } } - @UseGuards(AuthGuard) @Post() async create(@Body() pilotDto: PilotDto) { try { @@ -57,7 +56,6 @@ export class PilotController { } } - // @UseGuards(AuthGuard) @Put(':id') async update( @Param('id') id: string, @@ -72,7 +70,6 @@ export class PilotController { } } - // @UseGuards(AuthGuard) @Delete(':id') async delete( @Param('id') id: string, diff --git a/api/src/pilot/pilot.interceptor.ts b/api/src/pilot/pilot.interceptor.ts new file mode 100644 index 0000000..522c832 --- /dev/null +++ b/api/src/pilot/pilot.interceptor.ts @@ -0,0 +1,33 @@ +import { CallHandler, ExecutionContext, NestInterceptor, UnauthorizedException } from '@nestjs/common'; +import { jwtDecode } from 'jwt-decode'; +import { Observable, map } from 'rxjs'; +import { CustomJwtPayload } from 'src/interfaces/customJwtPayload.interface'; +import { PilotEntity } from './pilot.entity'; + +export class PilotInterceptor implements NestInterceptor { + intercept(context: ExecutionContext, handler: CallHandler): Observable { + const req = context.switchToHttp().getRequest(); + const authHeader = req.headers.authorization; + const token = authHeader && authHeader.split(' ')[1]; + const jwtPayload: CustomJwtPayload = jwtDecode(token); + + return handler.handle().pipe( + map((data: PilotEntity[]) => { + if (data.length && jwtPayload.roles.includes('Flying.Read')) { + const pilots = data.map((pilot) => { + return { + id: pilot.id, + name: pilot.name + }; + }); + + return pilots; + } else if (data.length && jwtPayload.roles.includes('Flying.Write')) { + return data; + } else { + return UnauthorizedException; + } + }) + ); + } +} diff --git a/api/src/track/track.controller.ts b/api/src/track/track.controller.ts index 7ec1a35..caff203 100644 --- a/api/src/track/track.controller.ts +++ b/api/src/track/track.controller.ts @@ -39,9 +39,11 @@ export class TrackController { // } // } + @UseGuards(AuthGuard) @Get(':logId') async findAll(@Param('logId') logId: string): Promise { try { + console.log('logId: ' + logId) return await this.trackService.findAll(logId); } catch (error) { const customError = error as CustomError; diff --git a/api/src/track/track.service.ts b/api/src/track/track.service.ts index 9b6a9d7..8460bff 100644 --- a/api/src/track/track.service.ts +++ b/api/src/track/track.service.ts @@ -55,6 +55,7 @@ export class TrackService { if (logEntity) { const url = await this.fileService.uploadFile(file, this.containerName, logId); + console.log(url) const track = this.trackRepository.create({ log: logEntity, order: order, diff --git a/client/package.json b/client/package.json index 3b16910..a2bc4f5 100644 --- a/client/package.json +++ b/client/package.json @@ -11,9 +11,14 @@ "serve": "serve -s dist" }, "dependencies": { - "@noahspan/noahspan-components": "^2.0.0-alpha-11", + "@fortawesome/fontawesome-svg-core": "^7.1.0", + "@fortawesome/free-solid-svg-icons": "^7.1.0", + "@fortawesome/react-fontawesome": "^3.1.0", + "@noahspan/noahspan-components": "^2.0.0-alpha-14", "@tailwindcss/vite": "^4.1.13", + "@tanstack/react-table": "^8.21.3", "axios": "^1.7.2", + "daisyui": "^5.1.10", "dotenv": "^16.4.7", "leaflet": "^1.9.4", "oidc-spa": "^7.2.4", diff --git a/client/src/App.tsx b/client/src/App.tsx index 3bc0f66..9cbb496 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -3,28 +3,22 @@ import Flights from './components/flights/Flights'; import Logbook from './components/logbook/Logbook'; import Pilots from './components/pilots/Pilots'; import SiteNav from './components/siteNav/SiteNav'; -import { useAuth } from 'react-oidc-context'; - -interface ProtectedRouteProps { - children: React.ReactNode; -} +import { HeroUIProvider } from '@heroui/react'; +import { useHref, useNavigate } from 'react-router-dom'; +import './styles.css'; const App = () => { - const auth = useAuth(); - - const ProtectedRoute = ({ children }: ProtectedRouteProps) => { - return auth.isAuthenticated ? children : - } + const navigate = useNavigate(); return ( - <> + } /> } /> } /> - + ); }; diff --git a/client/src/auth/oidcConfig.ts b/client/src/auth/oidcConfig.ts index 4a62670..651135f 100644 --- a/client/src/auth/oidcConfig.ts +++ b/client/src/auth/oidcConfig.ts @@ -1,8 +1,11 @@ import { createReactOidc } from "oidc-spa/react"; -export const { OidcProvider, useOidc, getOidc, withLoginEnforced, enforceLogin } = createReactOidc(async () => ({ +export const { OidcProvider, useOidc, getOidc } = createReactOidc(async () => ({ issuerUri: `https://login.microsoftonline.com/${import.meta.env.VITE_TENANT_ID}/v2.0`, clientId: import.meta.env.VITE_CLIENT_APP_ID, homeUrl: import.meta.env.BASE_URL, - scopes: ['email', 'openid', 'profile', `api://${import.meta.env.VITE_API_APP_ID}/user_impersonation`] + scopes: ['email', 'openid', 'profile', `api://${import.meta.env.VITE_API_APP_ID}/user_impersonation`], + autoLogin: true, + postLoginRedirectUrl: '/', + noIframe: true })); \ No newline at end of file diff --git a/client/src/components/actionMenu/ActionMenu.tsx b/client/src/components/actionMenu/ActionMenu.tsx index 71e45b4..4988493 100644 --- a/client/src/components/actionMenu/ActionMenu.tsx +++ b/client/src/components/actionMenu/ActionMenu.tsx @@ -3,27 +3,42 @@ import { IActionMenuProps } from './IActionMenuProps'; import { IconButton, Icon, - IconName + IconName, + Dropdown } from '@noahspan/noahspan-components'; import { FormMode } from '../../enums/formMode'; import { useAuth } from 'react-oidc-context'; const ActionMenu = ({ id, onDelete, onOpenCloseForm, onOpenCloseTracks }: IActionMenuProps) => { - const [anchorElAction, setAnchorElAction] = useState( - null - ); - const auth = useAuth(); + // const [anchorElAction, setAnchorElAction] = useState( + // null + // ); + // const auth = useAuth(); - const onOpenActionMenu = (event: React.MouseEvent) => { - setAnchorElAction(event.currentTarget); - }; + // const onOpenActionMenu = (event: React.MouseEvent) => { + // setAnchorElAction(event.currentTarget); + // }; - const onCloseActionMenu = () => { - setAnchorElAction(null); - }; + // const onCloseActionMenu = () => { + // setAnchorElAction(null); + // }; + + const options = [ + 'Item 1', + 'Item 2' + ] return ( - <> + <> + console.log('clicked!')} + options={options} + > + + + + + //
// // diff --git a/client/src/components/flights/Flights.tsx b/client/src/components/flights/Flights.tsx index 6947086..075e05a 100644 --- a/client/src/components/flights/Flights.tsx +++ b/client/src/components/flights/Flights.tsx @@ -1,37 +1,56 @@ -import { Card, Skeleton } from "@noahspan/noahspan-components"; +import { Icon, IconName, Skeleton } from "@noahspan/noahspan-components"; +import { Card } from '@heroui/react'; import LogbookCard from "../logbookCard/LogbookCard"; -import { useEffect, useState } from "react"; +import { useEffect, useReducer } from "react"; import { useLogs } from "../../hooks/logs/UseLogs"; -import { ILogbookEntry } from "../logbook/ILogbookEntry"; +import { LogbookEntry } from "../logbook/LogbookEntry.interface"; +import { initialState, reducer } from "./reducer"; +import { Alert } from '@heroui/react' const Flights = () => { - const [flights, setFlights] = useState([]); - const { logs, isLoading } = useLogs(); + const [state, dispatch] = useReducer(reducer, initialState); + const { logs, logsLoading } = useLogs(); useEffect(() => { - console.log(logs) - const flights: ILogbookEntry[] | undefined = logs?.filter((log: ILogbookEntry) => { + const flights: LogbookEntry[] | undefined = logs?.filter((log: LogbookEntry) => { if (log.tracks && log.tracks.length > 0) { return log; } }) if (flights && flights.length > 0) { - setFlights(flights) + dispatch({ type: 'SET_FLIGHTS', payload: flights}) + } else { + dispatch({ type: 'SET_ALERT', payload: { severity: 'default', message: 'There are no flights' }}) } }, [logs]) + + useEffect(() => { + console.log(logsLoading) + }, [logsLoading]) return (

Flights

- {!isLoading && + {!logsLoading && state.alert && (
- + + dispatch({ type: 'SET_ALERT', payload: undefined }) + } + color={state.alert.severity} + title={state.alert.message} + /> +
+ )} + {!logsLoading && +
+
} - {!isLoading && [...Array(6)].map((_element, index) => { + {logsLoading && [...Array(6)].map((_element, index) => { return (
{ + switch (action.type) { + case 'SET_ALERT': { + return { + ...state, + alert: action.payload + } + } + case 'SET_FLIGHTS': { + return { + ...state, + flights: action.payload + } + } + case 'SET_IS_LOADING': { + return { + ...state, + isLoading: action.payload + } + } + default: { + return state + } + } +} \ No newline at end of file diff --git a/client/src/components/logForm/ILogFormProps.ts b/client/src/components/logForm/ILogFormProps.ts deleted file mode 100644 index 6e0ecb8..0000000 --- a/client/src/components/logForm/ILogFormProps.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { FormMode } from '../../enums/formMode'; - -export interface ILogFormProps { - logId?: string; - isDrawerOpen: boolean; - mode: FormMode; - onOpenClose: (mode: FormMode) => void; -} diff --git a/client/src/components/logForm/ILogFormState.ts b/client/src/components/logForm/ILogFormState.ts deleted file mode 100644 index 259c316..0000000 --- a/client/src/components/logForm/ILogFormState.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Alert } from "../../interfaces/Alert.interface"; - -export interface ILogFormState { - alert: Alert | undefined; - experienceCollapseOpen: boolean; - instrumentCollapseOpen: boolean; - isDisabled: boolean; - isLoading: boolean; - landingsCollapseOpen: boolean; - pilotOptions: { label: string; value: string }[]; - selectedPilotName: string; -} diff --git a/client/src/components/logForm/LogForm.tsx b/client/src/components/logForm/LogForm.tsx index 5e43d59..f63de4a 100644 --- a/client/src/components/logForm/LogForm.tsx +++ b/client/src/components/logForm/LogForm.tsx @@ -1,99 +1,30 @@ import React, { useEffect, useReducer } from 'react'; -import { - Accordion, - AccordionDetails, - AccordionSummary, - Collapse, - CollapseContent, - CollapseTitle, - Alert, - Button, - DatePicker, - Drawer, - Icon, - IconButton, - IconName, - Input, - Select -} from '@noahspan/noahspan-components'; -import { useForm, Controller, FormProvider } from 'react-hook-form'; -import { ILogFormProps } from './ILogFormProps'; +import { Alert, Button, DatePicker, Drawer, DrawerBody, DrawerContent, DrawerFooter, DrawerHeader, Input, NumberInput, Select, Selection, SelectItem, SharedSelection, Textarea } from '@heroui/react' +import { useForm, Controller, FormProvider, useFormContext } from 'react-hook-form'; +import { LogFormProps } from './LogFormProps.interface'; import { initialState, reducer } from './reducer'; import { AxiosError, AxiosResponse } from 'axios'; import { FormMode } from '../../enums/formMode'; import { usePilots } from '../../hooks/pilots/UsePilots'; -import httpClient from '../../httpClient/httpClient'; import { useOidc } from '../../auth/oidcConfig'; +import httpClient from '../../httpClient/httpClient'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faSave, faXmark } from '@fortawesome/free-solid-svg-icons' +import { parseAbsolute, parseDate, getLocalTimeZone, CalendarDate, ZonedDateTime } from '@internationalized/date'; +import { useLogbookContext } from '../../hooks/logbookContext/UseLogbookContext'; -const LogForm: React.FC = ({ - logId, - isDrawerOpen, - mode, - onOpenClose -}) => { +const LogForm = () => { const [state, dispatch] = useReducer(reducer, initialState); - const defaultValues = { - pilotId: '', - date: null, - aircraftMakeModel: '', - aircraftIdentity: '', - routeFrom: '', - routeTo: '', - durationOfFlight: null, - singleEngineLand: null, - simulatorAtd: null, - landingsDay: null, - landingsNight: null, - groundTrainingReceived: null, - flightTrainingReceived: null, - crossCountry: null, - night: null, - solo: null, - pilotInCommand: null, - instrumentActual: null, - instrumentSimulated: null, - instrumentApproaches: null, - instrumentHolds: null, - instrumentNavTrack: null, - notes: '' - }; - const methods = useForm(); + const { control, formState, reset, setValue } = useFormContext() const { pilots } = usePilots(); const { isUserLoggedIn } = useOidc(); - - const onCancel = () => { - methods.reset(defaultValues); - dispatch({ type: 'SET_IS_DISABLED', payload: false }); - onOpenClose(FormMode.CANCEL); - }; - - const onSubmit = async (data: unknown) => { - try { - dispatch({ type: 'SET_IS_LOADING', payload: true }); - - if (!logId) { - await httpClient.post(`api/logs`, data); - } else { - await httpClient.put(`api/logs/${logId}`, data); - } - - methods.reset(defaultValues); - dispatch({ type: 'SET_IS_DISABLED', payload: false }); - onOpenClose(FormMode.CANCEL); - } catch (error) { - const axiosError = error as AxiosError; - - dispatch({ type: 'SET_ALERT', payload: { severity: 'error', message: axiosError.message }}); - } finally { - dispatch({ type: 'SET_IS_LOADING', payload: false }); - } - }; + const logbookContext = useLogbookContext() useEffect(() => { - if (mode === FormMode.VIEW) { + if (logbookContext.state.formMode === FormMode.VIEW) { dispatch({ type: 'SET_IS_DISABLED', payload: true }); } - }, [mode]); + }, [logbookContext.state.formMode]); useEffect(() => { const getEntry = async () => { @@ -101,734 +32,712 @@ const LogForm: React.FC = ({ dispatch({ type: 'SET_IS_LOADING', payload: true }); const response: AxiosResponse = await httpClient.get( - `api/logs/${logId}` + `api/logs/${logbookContext.state.selectedLogId}` ); const log = response.data; - log.pilotId = log.pilot.id - methods.reset(log); + reset(log); } catch (error) { const axiosError = error as AxiosError; - dispatch({ type: 'SET_ALERT', payload: { severity: 'error', message: axiosError.message }}); + dispatch({ type: 'SET_ALERT', payload: { severity: 'danger', message: axiosError.message }}); } finally { dispatch({ type: 'SET_IS_LOADING', payload: false }); } }; - if (logId && isDrawerOpen) { + if (logbookContext.state.selectedLogId) { getEntry(); } - }, [logId]); + }, [logbookContext.state.selectedLogId]); useEffect(() => { if (pilots && FormMode.ADD) { const newPilotsOptions = pilots.map((pilot) => { return { + key: pilot.id, label: pilot.name, - value: pilot.id }; }); - + console.log(newPilotsOptions) dispatch({ type: 'SET_PILOT_OPTIONS', payload: newPilotsOptions }); } }, [pilots]); - return ( - - -
-
-
-

{`${mode.toString().toLowerCase().charAt(0).toUpperCase() + mode.toString().slice(1).toLowerCase()} Entry`}

-
-
- - - -
- {state.alert && ( -
- - dispatch({ type: 'SET_ALERT', payload: undefined }) - } - severity={state.alert.severity} - > - {state.alert.message} - -
- )} -
- Pilot * -
-
- { - return ( - { + setValue('pilotId', keys.currentKey); }} - /> -
-
- Date * -
-
- ( - - )} - /> -
-
- Aircraft Make and Model * -
-
- ( - - )} - /> -
- {isUserLoggedIn && - <> -
- Aircraft Identity * -
-
- ( - - )} - /> -
- - } -
- Route From -
-
- ( - - )} - /> -
-
- Route To -
-
- ( - - )} - /> -
-
- Duration Of Flight -
-
- ( - - )} - /> -
- {isUserLoggedIn && - <> -
- Single Engine Land -
-
- ( - - )} - /> -
- - } - {isUserLoggedIn && - <> -
- Simulator or ATD -
-
- ( - - )} - /> -
- - } - {isUserLoggedIn && -
- dispatch({ type: 'SET_LANDINGS_COLLAPSE_OPEN', payload: !state.landingsCollapseOpen })} - open={state.landingsCollapseOpen} - > - - Landings - - -
-
- Day -
-
- ( - - )} - /> -
-
- Night -
-
- ( - - )} - /> -
-
-
-
-
- } - {isUserLoggedIn && -
- dispatch({ type: 'SET_EXPERIENCE_COLLAPSE_OPEN', payload: !state.experienceCollapseOpen })} - open={state.experienceCollapseOpen} - > - - Type of Pilot Experience or Training - - -
-
- - Ground Training Received - -
-
- ( - - )} - /> -
-
- - Flight Training Received - -
-
- ( - - )} - /> -
-
- Cross Country -
-
- ( - - )} - /> -
-
- Night -
-
- ( - - )} - /> -
-
- Solo -
-
- ( - - )} - /> -
-
- Pilot in Command -
-
- ( - - )} - /> -
-
-
-
-
- } - {isUserLoggedIn && -
- dispatch({ type: 'SET_INSTRUMENT_COLLAPSE_OPEN', payload: !state.instrumentCollapseOpen })} - open={state.instrumentCollapseOpen} - > - - Instrument - - -
-
- Actual -
-
- ( - - )} - /> -
-
- Simulated -
-
- ( - - )} - /> -
-
- - Instrument Approaches - -
-
- ( - - )} - /> -
-
- Holds -
-
- ( - - )} - /> -
-
- Nav / Track -
-
- ( - - )} - /> -
-
-
-
-
- } -
- Notes -
-
- ( - - )} - /> -
-
- - {mode.toString() !== FormMode.VIEW && ( - - )} -
+ {state.pilotOptions?.map((pilotOption: { key: string; label: string, }) => { + return ( + {pilotOption.label} + ) + })} + + ); + }} + /> +
+
+ Date +
+
+ { + const parsedAbsoluteDate = value ? parseAbsolute(value, getLocalTimeZone()) : value + + return( + { + let date = selectedDate as CalendarDate; + + setValue('date', date.toDate(getLocalTimeZone()).toISOString()) + }} + size='lg' + value={parsedAbsoluteDate ? new CalendarDate(parsedAbsoluteDate.year, parsedAbsoluteDate.month, parsedAbsoluteDate.day) : value} + /> + ) + }} + /> +
+
+ Aircraft Make and Model +
+
+ ( + + )} + /> +
+ {isUserLoggedIn && + <> +
+ Aircraft Identity
-
-
-
+
+ ( + + )} + /> +
+ + } +
+ Route From +
+
+ ( + + )} + /> +
+
+ Route To +
+
+ ( + + )} + /> +
+
+ Duration Of Flight +
+
+ ( + + )} + /> +
+ {isUserLoggedIn && + <> +
+ Single Engine Land +
+
+ ( + + )} + /> +
+
+ Simulator or ATD +
+
+ ( + + )} + /> +
+
+

Landings

+
+
+ Day +
+
+ ( + + )} + /> +
+
+ Night +
+
+ ( + + )} + /> +
+
+

Type of Training or Experience

+
+
+ + Ground Training Received + +
+
+ ( + + )} + /> +
+
+ + Flight Training Received + +
+
+ ( + + )} + /> +
+
+ Cross Country +
+
+ ( + + )} + /> +
+
+ Night +
+
+ ( + + )} + /> +
+
+ Solo +
+
+ ( + + )} + /> +
+
+ Pilot in Command +
+
+ ( + + )} + /> +
+
+

Instrument

+
+
+ Actual +
+
+ ( + + )} + /> +
+
+ Simulated +
+
+ ( + + )} + /> +
+
+ + Instrument Approaches + +
+
+ ( + + )} + /> +
+
+ Holds +
+
+ ( + + )} + /> +
+
+ Nav / Track +
+
+ ( + + )} + /> +
+ + } +
+

Notes

+
+
+ ( +