From 2a596b29f7f3360d2d81707eccba58e619041171 Mon Sep 17 00:00:00 2001 From: Noah Spannbauer Date: Thu, 20 Nov 2025 19:08:24 -0600 Subject: [PATCH] updating terraform --- api/package.json | 2 +- api/src/certificate/certificate.entity.ts | 2 +- api/src/endorsement/endorsement.entity.ts | 2 +- api/src/file/file.service.ts | 25 +- api/src/log/log.controller.ts | 14 +- api/src/log/log.entity.ts | 4 +- api/src/log/log.interceptor.ts | 36 ++- api/src/log/log.module.ts | 16 +- api/src/log/log.service.ts | 9 +- api/src/medical/medical.entity.ts | 2 +- api/src/pilot/pilot.controller.ts | 22 +- api/src/pilot/pilot.entity.ts | 8 +- api/src/pilot/pilot.interceptor.ts | 44 ++-- api/src/pilot/pilot.module.ts | 29 --- api/src/track/track.controller.ts | 24 +- api/src/track/track.entity.ts | 2 +- api/src/track/track.service.ts | 24 +- client/src/auth/oidcConfig.ts | 2 +- .../confirmationDialog/ConfirmationDialog.tsx | 68 +++--- ...s => ConfirmationDialogProps.interface.ts} | 2 +- client/src/components/flights/Flights.tsx | 3 +- client/src/components/logForm/LogForm.tsx | 4 - client/src/components/logbook/Logbook.tsx | 26 +-- client/src/components/logbook/columns.tsx | 1 - client/src/components/logbook/reducer.ts | 12 +- .../components/logbookCard/LogbookCard.tsx | 128 +++++----- .../logbookDrawer/LogbookDrawer.tsx | 97 +++++--- client/src/components/pilotForm/PilotForm.tsx | 88 +++---- client/src/components/pilotForm/states.ts | 198 ++++++++++++++++ client/src/components/pilots/Pilots.tsx | 102 +++----- client/src/components/siteNav/SiteNav.tsx | 166 +++++-------- .../components/stateSelect/StateSelect.tsx | 0 .../TrackMap.css} | 0 .../TrackMap.tsx} | 40 ++-- .../TrackMapProps.interface.ts} | 3 +- .../src/components/tracksForm/TracksForm.tsx | 171 +++++++------- .../tracksForm/TracksFormState.interface.ts | 4 +- client/src/components/tracksForm/reducer.ts | 10 +- .../src/hooks/featureFlag/UseFeatureFlag.tsx | 10 - client/src/hooks/pilots/UsePilots.tsx | 2 +- client/src/main.tsx | 20 +- client/src/styles.css | 8 +- database/flying.db | Bin 94208 -> 61440 bytes infrastructure/config/app/main.tf | 9 +- infrastructure/config/app/outputs.tf | 4 + .../config/container_app_environment/main.tf | 11 - .../container_app_environment/outputs.tf | 7 - .../container_app_environment/variables.tf | 1 - infrastructure/config/main.tf | 10 - infrastructure/config/outputs.tf | 8 - infrastructure/container_app.tf | 165 +++++++++++++ .../container_app_environment_storage.tf | 8 + infrastructure/main.tf | 218 +----------------- infrastructure/storage.tf | 19 ++ infrastructure/variables.tf | 23 -- package-lock.json | 28 ++- 56 files changed, 1008 insertions(+), 933 deletions(-) rename client/src/components/confirmationDialog/{IConfirmationDialogProps.ts => ConfirmationDialogProps.interface.ts} (75%) create mode 100644 client/src/components/pilotForm/states.ts create mode 100644 client/src/components/stateSelect/StateSelect.tsx rename client/src/components/{logTrackMaps/LogTrackMaps.css => trackMap/TrackMap.css} (100%) rename client/src/components/{logTrackMaps/LogTrackMaps.tsx => trackMap/TrackMap.tsx} (57%) rename client/src/components/{logTrackMaps/LogTrackMapsProps.interface.ts => trackMap/TrackMapProps.interface.ts} (58%) delete mode 100644 client/src/hooks/featureFlag/UseFeatureFlag.tsx delete mode 100644 infrastructure/config/container_app_environment/main.tf delete mode 100644 infrastructure/config/container_app_environment/outputs.tf delete mode 100644 infrastructure/config/container_app_environment/variables.tf create mode 100644 infrastructure/container_app.tf create mode 100644 infrastructure/container_app_environment_storage.tf create mode 100644 infrastructure/storage.tf diff --git a/api/package.json b/api/package.json index f9a6e7d..4e51c07 100644 --- a/api/package.json +++ b/api/package.json @@ -35,7 +35,7 @@ "@nestjs/serve-static": "^5.0.3", "@nestjs/typeorm": "^11.0.0", "@noahspan/azure-database": "^3.1.2", - "@noahspan/noahspan-modules": "^1.2.8", + "@noahspan/noahspan-modules": "^1.2.9", "@schematics/angular": "^17.3.7", "@types/multer": "^1.4.12", "better-sqlite3": "^12.2.0", diff --git a/api/src/certificate/certificate.entity.ts b/api/src/certificate/certificate.entity.ts index 6e670d3..0a1acbc 100644 --- a/api/src/certificate/certificate.entity.ts +++ b/api/src/certificate/certificate.entity.ts @@ -15,7 +15,7 @@ export class CertificateEntity { @Column() issueDate: Date - @ManyToOne(() => PilotEntity, (pilot: PilotEntity) => pilot.certificates) + @ManyToOne(() => PilotEntity, (pilot: PilotEntity) => pilot.certificates, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) @JoinColumn({ name: 'pilotId' }) pilot: PilotEntity; } \ No newline at end of file diff --git a/api/src/endorsement/endorsement.entity.ts b/api/src/endorsement/endorsement.entity.ts index 0c766cd..240e40b 100644 --- a/api/src/endorsement/endorsement.entity.ts +++ b/api/src/endorsement/endorsement.entity.ts @@ -12,7 +12,7 @@ export class EndorsementEntity { @Column() issueDate: Date; - @ManyToOne(() => PilotEntity, (pilot: PilotEntity) => pilot.endorsements) + @ManyToOne(() => PilotEntity, (pilot: PilotEntity) => pilot.endorsements, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) @JoinColumn({ name: 'pilotId' }) pilot: PilotEntity } \ No newline at end of file diff --git a/api/src/file/file.service.ts b/api/src/file/file.service.ts index 9ae05fa..3c82e58 100644 --- a/api/src/file/file.service.ts +++ b/api/src/file/file.service.ts @@ -58,11 +58,32 @@ import { ConfigService } from '@nestjs/config'; return downloaded } - async deleteFile(containerName: string, rowKey:string, fileName: string): Promise { + async deleteFile(containerName: string, logId: string, fileName: string): Promise { this.containerName = containerName; - const blockBlobClient = await this.getBlobClient(`${rowKey}/${fileName}`); + const blockBlobClient = await this.getBlobClient(`${logId}/${fileName}`); await blockBlobClient.deleteIfExists(); } + + async deleteFolder(containerName: string, logId: string): Promise { + const blobService = await this.getBlobServiceInstance(); + + this.containerName = containerName; + + const containerClient = blobService.getContainerClient(containerName); + const blobsToDelete = [] + + for await (const blob of containerClient.listBlobsFlat({ prefix: logId })) { + blobsToDelete.push(blob.name) + } + + for (const blobName of blobsToDelete) { + const blockBlobClient = containerClient.getBlockBlobClient(blobName); + + await blockBlobClient.delete(); + } + + return; + } } \ No newline at end of file diff --git a/api/src/log/log.controller.ts b/api/src/log/log.controller.ts index f139cf6..2e6dd62 100644 --- a/api/src/log/log.controller.ts +++ b/api/src/log/log.controller.ts @@ -14,14 +14,16 @@ import { LogDto } from './log.dto'; import { LogEntity } from './log.entity'; import { LogService } from './log.service'; import { CustomError } from '../error/customError'; -import { AuthGuard } from '@noahspan/noahspan-modules'; +import { AuthGuard, Public } from '@noahspan/noahspan-modules'; import { LogInterceptor } from './log.interceptor'; import { FileService } from '../file/file.service'; import { DeleteResult, InsertResult, UpdateResult } from 'typeorm'; +import { Reflector } from '@nestjs/core'; + +const reflector = new Reflector(); @Controller('logs') -@UseInterceptors(new LogInterceptor()) -// @UseGuards(AuthGuard) +@UseInterceptors(new LogInterceptor(reflector)) export class LogController { constructor( private readonly fileService: FileService, @@ -30,6 +32,7 @@ export class LogController { @Get(':id') + @Public() async find( @Param('id') id: string, ): Promise { @@ -44,6 +47,7 @@ export class LogController { } @Get() + @Public() async findAll(): Promise { try { return await this.logService.findAll(); @@ -56,6 +60,7 @@ export class LogController { @Post() + @UseGuards(AuthGuard) async create(@Body() logDto: LogDto): Promise { try { return await this.logService.create(logDto); @@ -67,6 +72,7 @@ export class LogController { } @Put(':id') + @UseGuards(AuthGuard) async update( @Param('id') id: string, @Body() logDto: LogDto @@ -83,12 +89,14 @@ export class LogController { } @Delete(':id') + @UseGuards(AuthGuard) async delete( @Param('id') id: string, ): Promise { try { return await this.logService.delete(id); } catch (error) { + console.log(error) const customError = error as CustomError; throw new HttpException(customError.message, customError.statusCode); diff --git a/api/src/log/log.entity.ts b/api/src/log/log.entity.ts index 884645f..4c9fe24 100644 --- a/api/src/log/log.entity.ts +++ b/api/src/log/log.entity.ts @@ -76,10 +76,10 @@ export class LogEntity { @Column({ nullable: true }) notes: string | null; - @OneToMany(() => TrackEntity, (track: TrackEntity) => track.log, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + @OneToMany(() => TrackEntity, (track: TrackEntity) => track.log) tracks: TrackEntity[] - @ManyToOne(() => PilotEntity, (pilot: PilotEntity) => pilot.logs) + @ManyToOne(() => PilotEntity, (pilot: PilotEntity) => pilot.logs, {onDelete: 'CASCADE', onUpdate: 'CASCADE'}) @JoinColumn({ name: 'pilotId' }) pilot: PilotEntity; } \ No newline at end of file diff --git a/api/src/log/log.interceptor.ts b/api/src/log/log.interceptor.ts index f10f6f9..15676dc 100644 --- a/api/src/log/log.interceptor.ts +++ b/api/src/log/log.interceptor.ts @@ -3,18 +3,23 @@ import { Observable, map } from 'rxjs'; import { LogEntity } from './log.entity'; import { jwtDecode } from 'jwt-decode'; import { CustomJwtPayload } from 'src/interfaces/customJwtPayload.interface'; +import { IS_PUBLIC_KEY } from '@noahspan/noahspan-modules'; +import { Reflector } from '@nestjs/core'; export class LogInterceptor implements NestInterceptor { + constructor(private reflector: Reflector) {} + 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); + const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ + context.getHandler(), + context.getClass(), + ]); return handler.handle().pipe( map((data: LogEntity[]) => { - if (data.length > 0 && jwtPayload.roles.includes('Flying.Read')) { - const logs = data.map((log: LogEntity) => { + const req = context.switchToHttp().getRequest(); + const limitData = (data) => { + return data.map((log: LogEntity) => { return { id: log.id, pilot: { @@ -28,10 +33,25 @@ export class LogInterceptor implements NestInterceptor { tracks: log.tracks, }; }); + } + + if (req.headers.authorization) { + const authHeader = req.headers.authorization; + const token = authHeader && authHeader.split(' ')[1]; + const jwtPayload: CustomJwtPayload = jwtDecode(token); + + if (jwtPayload.roles.includes('Flying.Read')) { + const logs = limitData(data); + + return logs; + } else { + return data; + } + } else if (!req.headers.authorization && isPublic) { + const publicData = limitData(data) + const logs = publicData.slice(0, 5) return logs; - } else { - return data; } }) ); diff --git a/api/src/log/log.module.ts b/api/src/log/log.module.ts index c08d8c7..b2a93af 100644 --- a/api/src/log/log.module.ts +++ b/api/src/log/log.module.ts @@ -1,28 +1,14 @@ import { Module } from '@nestjs/common'; import { LogController } from './log.controller'; import { LogService } from './log.service'; -// import { AzureTableStorageModule } from '@noahspan/azure-database'; import { LogEntity } from './log.entity'; -import { ConfigModule, ConfigService } from '@nestjs/config'; +import { ConfigService } from '@nestjs/config'; import { FileService } from '../file/file.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { PilotModule } from 'src/pilot/pilot.module'; @Module({ imports: [ - // AzureTableStorageModule.forRootAsync({ - // imports: [ConfigModule], - // useFactory: async (configService: ConfigService) => { - // return { - // connectionString: configService.get('azureStorageConnectionString') - // }; - // }, - // inject: [ConfigService] - // }), - // AzureTableStorageModule.forFeature(Log, { - // createTableIfNotExists: false, - // table: 'logs' - // }), PilotModule, TypeOrmModule.forFeature([LogEntity]) ], diff --git a/api/src/log/log.service.ts b/api/src/log/log.service.ts index 2076da2..ba46213 100644 --- a/api/src/log/log.service.ts +++ b/api/src/log/log.service.ts @@ -6,19 +6,20 @@ import { LogDto } from './log.dto'; import { PilotService } from 'src/pilot/pilot.service'; import { PilotEntity } from 'src/pilot/pilot.entity'; import { CustomError } from 'src/error/customError'; -import { TrackService } from 'src/track/track.service'; +import { FileService } from 'src/file/file.service'; @Injectable() export class LogService { constructor( @InjectRepository(LogEntity) private readonly logRepository: Repository, - private readonly pilotService: PilotService + private readonly fileService: FileService, + private readonly pilotService: PilotService, ) {} async find(id: string): Promise { const logEntity: LogEntity = await this.logRepository.findOne({ where: { id: id }, - // relations: ['pilot', 'tracks'] + relations: ['pilot', 'tracks'] }); return logEntity; @@ -55,6 +56,8 @@ export class LogService { } async delete(id: string): Promise { + await this.fileService.deleteFolder('tracks', id); + return await this.logRepository.delete({ id }); } } diff --git a/api/src/medical/medical.entity.ts b/api/src/medical/medical.entity.ts index df590c3..02550fc 100644 --- a/api/src/medical/medical.entity.ts +++ b/api/src/medical/medical.entity.ts @@ -12,7 +12,7 @@ export class MedicalEntity { @Column() expirationDate: Date; - @ManyToOne(() => PilotEntity, (pilot: PilotEntity) => pilot.medical) + @ManyToOne(() => PilotEntity, (pilot: PilotEntity) => pilot.medical, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) @JoinColumn({ name: 'pilotId' }) pilot: PilotEntity; } \ No newline at end of file diff --git a/api/src/pilot/pilot.controller.ts b/api/src/pilot/pilot.controller.ts index a3182d7..3ecc715 100644 --- a/api/src/pilot/pilot.controller.ts +++ b/api/src/pilot/pilot.controller.ts @@ -11,19 +11,21 @@ import { UseInterceptors, } from '@nestjs/common'; import { PilotDto } from './pilot.dto'; -import { PilotEntity } from './pilot.entity'; import { PilotService } from './pilot.service'; import { CustomError } from '../error/customError'; import { PilotInterceptor } from './pilot.interceptor'; -import { AuthGuard } from '@noahspan/noahspan-modules'; +import { AuthGuard, Public } from '@noahspan/noahspan-modules'; +import { Reflector } from '@nestjs/core'; + +const reflector = new Reflector(); @Controller('pilots') -@UseInterceptors(new PilotInterceptor()) -@UseGuards(AuthGuard) +@UseInterceptors(new PilotInterceptor(reflector)) export class PilotController { constructor(private readonly pilotService: PilotService) {} @Get(':id') + @Public() async find(@Param('id') id: string) { try { return await this.pilotService.find(id); @@ -35,28 +37,33 @@ export class PilotController { } @Get() + @Public() async findAll() { try { return await this.pilotService.findAll(); } catch (error) { const customError = error as CustomError; - + console.log(error) throw new HttpException(customError.message, customError.statusCode); } } @Post() + @UseGuards(AuthGuard) async create(@Body() pilotDto: PilotDto) { try { + return await this.pilotService.create(pilotDto); } catch (error) { - const customError = error as CustomError; + console.log(error) + // const customError = error as CustomError; - throw new HttpException(customError.message, customError.statusCode); + // throw new HttpException(customError.message, customError.statusCode); } } @Put(':id') + @UseGuards(AuthGuard) async update( @Param('id') id: string, @Body() pilotDto: PilotDto @@ -71,6 +78,7 @@ export class PilotController { } @Delete(':id') + @UseGuards(AuthGuard) async delete( @Param('id') id: string, ) { diff --git a/api/src/pilot/pilot.entity.ts b/api/src/pilot/pilot.entity.ts index cf18f2d..d78a017 100644 --- a/api/src/pilot/pilot.entity.ts +++ b/api/src/pilot/pilot.entity.ts @@ -33,15 +33,15 @@ export class PilotEntity { @Column() userId: string | null; - @OneToMany(() => LogEntity, (log: LogEntity) => log.pilot, {onDelete: 'CASCADE', onUpdate: 'CASCADE'}) + @OneToMany(() => LogEntity, (log: LogEntity) => log.pilot) logs: LogEntity[]; - @OneToMany(() => CertificateEntity, (certificate: CertificateEntity) => certificate.pilot, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + @OneToMany(() => CertificateEntity, (certificate: CertificateEntity) => certificate.pilot) certificates: CertificateEntity[]; - @OneToMany(() => EndorsementEntity, (endorsement: EndorsementEntity) => endorsement.pilot, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + @OneToMany(() => EndorsementEntity, (endorsement: EndorsementEntity) => endorsement.pilot) endorsements: EndorsementEntity[]; - @OneToMany(() => MedicalEntity, (medical: MedicalEntity) => medical.pilot, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + @OneToMany(() => MedicalEntity, (medical: MedicalEntity) => medical.pilot) medical: MedicalEntity[]; } diff --git a/api/src/pilot/pilot.interceptor.ts b/api/src/pilot/pilot.interceptor.ts index 522c832..5ef7036 100644 --- a/api/src/pilot/pilot.interceptor.ts +++ b/api/src/pilot/pilot.interceptor.ts @@ -1,31 +1,49 @@ -import { CallHandler, ExecutionContext, NestInterceptor, UnauthorizedException } from '@nestjs/common'; +import { CallHandler, ExecutionContext, NestInterceptor } 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'; +import { IS_PUBLIC_KEY } from '@noahspan/noahspan-modules'; +import { Reflector } from '@nestjs/core'; export class PilotInterceptor implements NestInterceptor { + constructor(private reflector: Reflector) {} + 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); + const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ + context.getHandler(), + context.getClass(), + ]); return handler.handle().pipe( map((data: PilotEntity[]) => { - if (data.length && jwtPayload.roles.includes('Flying.Read')) { - const pilots = data.map((pilot) => { + const req = context.switchToHttp().getRequest(); + const limitData = (data) => { + return data.map((pilot: PilotEntity) => { return { id: pilot.id, name: pilot.name }; - }); + }) + } - return pilots; - } else if (data.length && jwtPayload.roles.includes('Flying.Write')) { - return data; - } else { - return UnauthorizedException; + if (req.headers.authorization) { + const authHeader = req.headers.authorization; + const token = authHeader && authHeader.split(' ')[1]; + const jwtPayload: CustomJwtPayload = jwtDecode(token); + + if (jwtPayload.roles.includes('Flying.Read')) { + const pilots = limitData(data) + + return pilots; + } else { + return data; + } + } else if (!req.headers.authorization && isPublic) { + const publicData = limitData(data); + const logs = publicData.slice(0,5) + + return logs; } }) ); diff --git a/api/src/pilot/pilot.module.ts b/api/src/pilot/pilot.module.ts index b6d0aaf..7fea858 100644 --- a/api/src/pilot/pilot.module.ts +++ b/api/src/pilot/pilot.module.ts @@ -1,40 +1,11 @@ import { Module } from '@nestjs/common'; import { PilotController } from './pilot.controller'; import { PilotService } from './pilot.service'; -// import { AzureTableStorageModule } from '@noahspan/azure-database'; import { PilotEntity } from './pilot.entity'; -// import { ConfigModule, ConfigService } from '@nestjs/config'; -// import { Log } from 'src/log/log.entity'; import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ imports: [ - // AzureTableStorageModule.forRootAsync({ - // imports: [ConfigModule], - // useFactory: async (configService: ConfigService) => { - // return { - // connectionString: configService.get('azureStorageConnectionString') - // }; - // }, - // inject: [ConfigService] - // }), - // AzureTableStorageModule.forFeature(Log, { - // createTableIfNotExists: false, - // table: 'logs' - // }), - // AzureTableStorageModule.forRootAsync({ - // imports: [ConfigModule], - // useFactory: async (configService: ConfigService) => { - // return { - // connectionString: configService.get('azureStorageConnectionString') - // }; - // }, - // inject: [ConfigService] - // }), - // AzureTableStorageModule.forFeature(PilotEntity, { - // createTableIfNotExists: false, - // table: 'pilots' - // }), TypeOrmModule.forFeature([PilotEntity]) ], controllers: [PilotController], diff --git a/api/src/track/track.controller.ts b/api/src/track/track.controller.ts index caff203..594e8a6 100644 --- a/api/src/track/track.controller.ts +++ b/api/src/track/track.controller.ts @@ -1,12 +1,10 @@ import { - Body, Controller, Delete, Get, HttpException, Param, Post, - Put, Query, UploadedFile, UseGuards, @@ -18,8 +16,7 @@ import { FileInterceptor } from '@nestjs/platform-express'; import { FileService } from '../file/file.service'; import { TrackService } from './track.service'; import { TrackEntity } from './track.entity'; -import { TrackDto } from './track.dto'; -import { DeleteResult, InsertResult, UpdateResult } from 'typeorm'; +import { DeleteResult, InsertResult } from 'typeorm'; @Controller('tracks') export class TrackController { @@ -28,22 +25,10 @@ export class TrackController { private readonly trackService: TrackService ) {} - // @Get('id') - // async find(@Param('id') id: string): Promise { - // try { - // return await this.trackService.find(id); - // } catch (error) { - // const customError = error as CustomError; - - // throw new HttpException(customError.message, customError.statusCode); - // } - // } - @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; @@ -66,11 +51,10 @@ export class TrackController { } @UseGuards(AuthGuard) - @Delete(':id') - async delete(@Param('id') id: string, @Query('fileName') fileName: string, @Query('logId') logId: string): Promise { + @Delete(':id/:filename/:logId') + async delete(@Param('id') id: string, @Query('fileName') filename: string, @Query('logId') logId: string): Promise { try { - - return await this.trackService.delete(id, logId, fileName); + return await this.trackService.delete(id, logId, filename); } catch (error) { const customError = error as CustomError; diff --git a/api/src/track/track.entity.ts b/api/src/track/track.entity.ts index d25eab8..af3e65a 100644 --- a/api/src/track/track.entity.ts +++ b/api/src/track/track.entity.ts @@ -12,7 +12,7 @@ export class TrackEntity { @Column() order: number; - @ManyToOne(() => LogEntity, (log: LogEntity) => log.tracks) + @ManyToOne(() => LogEntity, (log: LogEntity) => log.tracks, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) @JoinColumn({ name: 'logId' }) log: LogEntity; } \ No newline at end of file diff --git a/api/src/track/track.service.ts b/api/src/track/track.service.ts index 8460bff..9414905 100644 --- a/api/src/track/track.service.ts +++ b/api/src/track/track.service.ts @@ -31,7 +31,6 @@ export class TrackService { const logEntity: LogEntity = await this.logService.find(logId); if (logEntity) { - console.log(logEntity) const tracks = await this.trackRepository.find({ where: { log: { @@ -40,7 +39,6 @@ export class TrackService { }, }) - console.log(tracks) return tracks; } } catch (error) { @@ -55,7 +53,6 @@ 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, @@ -67,7 +64,6 @@ export class TrackService { throw new CustomError('Log not found', 'Not found', 404) } } catch (error) { - console.log(error) throw error; } } @@ -80,16 +76,6 @@ export class TrackService { } } - async delete(id: string, logId: string, fileName: string): Promise { - try { - await this.fileService.deleteFile(this.containerName, logId, fileName); - - return await this.trackRepository.delete({ id }); - } catch (error) { - throw error - } - } - async downloadTrackFile(logId: string, fileName: string): Promise { try { const downloadedFile: string = await this.fileService.downloadFile(this.containerName, logId, fileName); @@ -99,4 +85,14 @@ export class TrackService { throw error } } + + async delete(id: string, logId: string, fileName: string): Promise { + try { + await this.fileService.deleteFile(this.containerName, logId, fileName); + + return await this.trackRepository.delete({ id }); + } catch (error) { + throw error + } + } } \ No newline at end of file diff --git a/client/src/auth/oidcConfig.ts b/client/src/auth/oidcConfig.ts index 651135f..8e08f73 100644 --- a/client/src/auth/oidcConfig.ts +++ b/client/src/auth/oidcConfig.ts @@ -5,7 +5,7 @@ export const { OidcProvider, useOidc, getOidc } = createReactOidc(async () => ({ 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`], - autoLogin: true, + autoLogin: false, postLoginRedirectUrl: '/', noIframe: true })); \ No newline at end of file diff --git a/client/src/components/confirmationDialog/ConfirmationDialog.tsx b/client/src/components/confirmationDialog/ConfirmationDialog.tsx index c327613..2a467de 100644 --- a/client/src/components/confirmationDialog/ConfirmationDialog.tsx +++ b/client/src/components/confirmationDialog/ConfirmationDialog.tsx @@ -1,13 +1,16 @@ -import { - Button, - Dialog, - DialogActions, - DialogContent, - Icon, - IconName, - Loading -} from '@noahspan/noahspan-components'; -import { IDialogConfirmationProps } from './IConfirmationDialogProps'; +// import { +// Button, +// Dialog, +// DialogActions, +// DialogContent, +// Icon, +// IconName, +// Loading +// } from '@noahspan/noahspan-components'; +import { Button, Modal, ModalBody, ModalContent, ModalHeader, ModalFooter, Spinner } from '@heroui/react' +import { DialogConfirmationProps } from './ConfirmationDialogProps.interface'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faCircleCheck, faXmark } from '@fortawesome/free-solid-svg-icons'; const ConfirmationDialog = ({ contentText, @@ -16,28 +19,33 @@ const ConfirmationDialog = ({ onCancel, onConfirm, title -}: IDialogConfirmationProps) => { +}: DialogConfirmationProps) => { return ( - -

{title}

- - {!isLoading &&
{contentText}
} - {isLoading && } -
- - - - -
+ + {title} + + {!isLoading &&
{contentText}
} + {isLoading && } +
+ + + + +
+ ); }; diff --git a/client/src/components/confirmationDialog/IConfirmationDialogProps.ts b/client/src/components/confirmationDialog/ConfirmationDialogProps.interface.ts similarity index 75% rename from client/src/components/confirmationDialog/IConfirmationDialogProps.ts rename to client/src/components/confirmationDialog/ConfirmationDialogProps.interface.ts index 90cd809..1d7fd0e 100644 --- a/client/src/components/confirmationDialog/IConfirmationDialogProps.ts +++ b/client/src/components/confirmationDialog/ConfirmationDialogProps.interface.ts @@ -1,4 +1,4 @@ -export interface IDialogConfirmationProps { +export interface DialogConfirmationProps { contentText: string; isLoading: boolean; isOpen: boolean; diff --git a/client/src/components/flights/Flights.tsx b/client/src/components/flights/Flights.tsx index 075e05a..691b0f1 100644 --- a/client/src/components/flights/Flights.tsx +++ b/client/src/components/flights/Flights.tsx @@ -20,8 +20,9 @@ const Flights = () => { if (flights && flights.length > 0) { dispatch({ type: 'SET_FLIGHTS', payload: flights}) + dispatch({ type: 'SET_ALERT', payload: undefined }) } else { - dispatch({ type: 'SET_ALERT', payload: { severity: 'default', message: 'There are no flights' }}) + dispatch({ type: 'SET_ALERT', payload: { severity: 'default', message: 'No flights found' }}) } }, [logs]) diff --git a/client/src/components/logForm/LogForm.tsx b/client/src/components/logForm/LogForm.tsx index f63de4a..b9612ca 100644 --- a/client/src/components/logForm/LogForm.tsx +++ b/client/src/components/logForm/LogForm.tsx @@ -64,10 +64,6 @@ const LogForm = () => { } }, [pilots]); - useEffect(() => { - console.log(state.isDisabled) - }, [state.isDisabled]) - return (
diff --git a/client/src/components/logbook/Logbook.tsx b/client/src/components/logbook/Logbook.tsx index b828e2c..321859c 100644 --- a/client/src/components/logbook/Logbook.tsx +++ b/client/src/components/logbook/Logbook.tsx @@ -132,17 +132,11 @@ const Logbook: React.FC = () => { > View - onOpenCloseDrawer(FormMode.EDIT, info.row.original.id)} - startContent={} - > - Tracks - onDeleteLog(info.row.original.id)} startContent={} > Delete @@ -393,7 +387,6 @@ const Logbook: React.FC = () => { }; const onOpenCloseDrawer= (mode: FormMode, logId?: string) => { - console.log(logId) switch (mode) { case FormMode.ADD: case FormMode.EDIT: @@ -423,10 +416,8 @@ const Logbook: React.FC = () => { }; const onDeleteLog = (logId: string) => { - dispatch({ - type: 'SET_DELETE', - payload: { isConfirmationDialogOpen: true, selectedLogId: logId } - }); + dispatch({ type: 'SET_IS_CONFIRMATION_DIALOG_OPEN', payload: true }); + logbookContext.dispatch({ type: 'SET_SELECTED_LOG_ID', payload: logId }) }; const onConfirmationDialogConfirm = async () => { @@ -436,9 +427,10 @@ const Logbook: React.FC = () => { await httpClient.delete(`api/logs/${logbookContext.state.selectedLogId}`); dispatch({ - type: 'SET_DELETE', - payload: { isConfirmationDialogOpen: false, selectedLogId: undefined } + type: 'SET_IS_CONFIRMATION_DIALOG_OPEN', + payload: false }); + logbookContext.dispatch({ type: 'SET_SELECTED_LOG_ID', payload: '' }) await getLogbookEntries(); } catch (error) { const axiosError = error as AxiosError; @@ -454,8 +446,8 @@ const Logbook: React.FC = () => { const onConfirmationDialogCancel = () => { dispatch({ - type: 'SET_DELETE', - payload: { isConfirmationDialogOpen: false, selectedLogId: undefined } + type: 'SET_IS_CONFIRMATION_DIALOG_OPEN', + payload: false }); }; @@ -523,7 +515,7 @@ const Logbook: React.FC = () => { }
{!state.isLoading && state.alert && ( -
+
dispatch({ type: 'SET_ALERT', payload: undefined }) diff --git a/client/src/components/logbook/columns.tsx b/client/src/components/logbook/columns.tsx index 6c80ad5..37315ce 100644 --- a/client/src/components/logbook/columns.tsx +++ b/client/src/components/logbook/columns.tsx @@ -34,7 +34,6 @@ const date: ColumnDef = { header: 'Date', cell: (info: CellContext) => { const date = new Date(info.getValue() as string); - console.log(date) const formattedDate = `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}` return formattedDate; diff --git a/client/src/components/logbook/reducer.ts b/client/src/components/logbook/reducer.ts index a470bca..43bb230 100644 --- a/client/src/components/logbook/reducer.ts +++ b/client/src/components/logbook/reducer.ts @@ -6,13 +6,7 @@ import { LogbookState } from './LogbookState.interface'; type Action = | { type: 'SET_COLUMNS'; payload: ColumnDef[] } - | { - type: 'SET_DELETE'; - payload: { - isConfirmationDialogOpen: boolean; - selectedLogId: string | undefined; - }; - } + | { type: 'SET_IS_CONFIRMATION_DIALOG_OPEN'; payload: boolean } | { type: 'SET_ENTRIES'; payload: LogbookEntry[] } | { type: 'SET_ALERT'; payload: Alert | undefined } | { type: 'SET_IS_CONFIRMATION_DIALOG_LOADING'; payload: boolean } @@ -39,10 +33,10 @@ export const reducer = ( columns: action.payload } } - case 'SET_DELETE': { + case 'SET_IS_CONFIRMATION_DIALOG_OPEN': { return { ...state, - isConfirmDialogOpen: action.payload.isConfirmationDialogOpen, + isConfirmDialogOpen: action.payload, }; } case 'SET_ENTRIES': { diff --git a/client/src/components/logbookCard/LogbookCard.tsx b/client/src/components/logbookCard/LogbookCard.tsx index e32b353..8fdce5d 100644 --- a/client/src/components/logbookCard/LogbookCard.tsx +++ b/client/src/components/logbookCard/LogbookCard.tsx @@ -1,86 +1,72 @@ -import { Card, CardBody, CardContent, CardHeader } from "@noahspan/noahspan-components"; +import { Accordion, AccordionItem, Card, CardBody, CardHeader } from '@heroui/react' import { LogbookCardProps } from "./LogbookCardProps.interface"; -import ActionMenu from "../actionMenu/ActionMenu"; -import LogTrackMaps from "../logTrackMaps/LogTrackMaps"; - +import TrackMap from "../trackMap/TrackMap"; const LogbookCard = ({ logs, mode, onDelete, onOpenCloseForm }: LogbookCardProps) => { return (
{logs.map((log) => { + console.log(log) const date = new Date(log.date); const formattedDate: string = `${date.getMonth()}/${date.getDate()}/${date.getFullYear()}`; return (
- - - - -
- {mode === 'flights' && log.tracks && log.tracks.length > 0 && -
- -
- } -
- Aircraft Make and Model -
-
- {log.aircraftMakeModel} -
-
- Route From -
-
- {log.routeFrom} -
-
- Route To -
-
- {log.routeTo} -
-
- Duration Of Flight -
-
- {log.durationOfFlight} -
- {mode === 'logbook' && log.tracks && log.tracks.length > 0 && - <> -
- Tracks -
- {log.tracks.map((track: { id: string; order: number; url: string}) => { - const trackSplit = track.url.split('/') - const filename = trackSplit[trackSplit.length - 1]; - - return ( -
- {filename} -
- ) - - })} - - } - {log.notes && - <> -
- Notes -
-
- {log.notes} -
- - } + + +

{formattedDate}

+
+ + {mode === 'flights' && log.tracks && log.tracks.length > 0 && +
+
- - + } + + +
+
+ Aircraft Make and Model +
+
+ {log.aircraftMakeModel} +
+
+ Route From +
+
+ {log.routeFrom} +
+
+ Route To +
+
+ {log.routeTo} +
+
+ Duration Of Flight +
+
+ {log.durationOfFlight} +
+ {log.notes && + <> +
+ Notes +
+
+ {log.notes} +
+ + } +
+
+
+
diff --git a/client/src/components/logbookDrawer/LogbookDrawer.tsx b/client/src/components/logbookDrawer/LogbookDrawer.tsx index 4e545f2..ebb3f32 100644 --- a/client/src/components/logbookDrawer/LogbookDrawer.tsx +++ b/client/src/components/logbookDrawer/LogbookDrawer.tsx @@ -9,8 +9,10 @@ import { FormMode } from "../../enums/formMode"; import httpClient from "../../httpClient/httpClient"; import { AxiosError } from "axios"; import { useLogbookContext } from "../../hooks/logbookContext/UseLogbookContext"; +import { Key, useState } from "react"; const LogbookDrawer = ({ onOpenClose }: LogbookDrawerProps) => { + const [activeTab, setActiveTab] = useState('time'); const defaultValues = { pilotId: '', date: null, @@ -34,8 +36,7 @@ const LogbookDrawer = ({ onOpenClose }: LogbookDrawerProps) => { instrumentApproaches: null, instrumentHolds: null, instrumentNavTrack: null, - notes: '', - tracks: [] + notes: '' }; const methods = useForm(); const logbookContext = useLogbookContext() @@ -69,9 +70,19 @@ const LogbookDrawer = ({ onOpenClose }: LogbookDrawerProps) => { } }; + const onSelectedKeyChanged = (key: React.Key) => { + setActiveTab(key) + } + return ( + + + } isOpen={logbookContext.state.isDrawerOpen} + onClose={onCancel} > @@ -91,7 +102,13 @@ const LogbookDrawer = ({ onOpenClose }: LogbookDrawerProps) => { />
)} - + { > - - - Tracks -
- } - > - - + {logbookContext.state.formMode !== FormMode.ADD && + + + Tracks +
+ } + > + + + } - -
-
- - {logbookContext.state.formMode.toString() !== FormMode.VIEW && ( + {activeTab !== 'tracks' && + +
+
- )} + {logbookContext.state.formMode.toString() !== FormMode.VIEW && ( + + )} +
-
- + + } diff --git a/client/src/components/pilotForm/PilotForm.tsx b/client/src/components/pilotForm/PilotForm.tsx index 5027c22..9130706 100644 --- a/client/src/components/pilotForm/PilotForm.tsx +++ b/client/src/components/pilotForm/PilotForm.tsx @@ -1,15 +1,5 @@ -import { useEffect, useState } from 'react'; +import { Key, useEffect, useState } from 'react'; import { useForm, Controller, FormProvider } from 'react-hook-form'; -import { - // Button, - // Drawer, - Icon, - IconButton, - IconName, - // Input, - PeoplePicker, - StateSelect -} from '@noahspan/noahspan-components'; import { IPilotFormProps } from './IPilotFormProps'; import { AxiosError, AxiosResponse } from 'axios'; import { FormMode } from '../../enums/formMode'; @@ -18,11 +8,11 @@ import PilotFormCertificates from '../pilotFormCertificates/PilotFormCertificate import PilotFormEndorsements from '../pilotFormEndorsements/PilotFormEndorsements'; import PilotFormMedical from '../pilotFormMedical/PilotFormMedical'; import { useOidc } from '../../auth/oidcConfig'; -import { getOidc } from '../../auth/oidcConfig'; import httpClient from '../../httpClient/httpClient'; -import { Button, Drawer, DrawerHeader, DrawerContent, DrawerBody, DrawerFooter, Input } from '@heroui/react' +import { Button, Drawer, DrawerHeader, DrawerContent, DrawerBody, DrawerFooter, Input, Autocomplete, AutocompleteItem, SelectItem, Select } from '@heroui/react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faXmark } from '@fortawesome/free-solid-svg-icons' +import { faSave, faXmark } from '@fortawesome/free-solid-svg-icons'; +import { states } from './states'; const PilotForm: React.FC = ({ pilotId, @@ -31,7 +21,7 @@ const PilotForm: React.FC = ({ onOpenClose }: IPilotFormProps) => { const [peoplePickerValue, setPeoplePickerValue] = useState(''); - const [peoplePickerResults, setPeoplePickerResults] = useState([]); + const [peoplePickerResults, setPeoplePickerResults] = useState([]); const [isPeoplePickerLoading, setIsPeoplePickerLoading] = useState(false); const [selectedPerson, setSelectedPerson] = useState({ @@ -47,6 +37,7 @@ const PilotForm: React.FC = ({ postalCode: '', email: '', phone: '', + userId: '' }; const methods = useForm({ defaultValues: defaultValues @@ -63,15 +54,10 @@ const PilotForm: React.FC = ({ try { if (value !== '') { const searchString: string = value; - const oidc = await getOidc(); const response: AxiosResponse = await httpClient.get( - `api/msgraph/search?search=${searchString}`, { - headers: { - Authorization: oidc.isUserLoggedIn ? `Bearer ${(await oidc.getTokens()).accessToken}` : '' - } - } + `api/msgraph/search?search=${searchString}` ); - console.log(response) + setPeoplePickerResults(response.data); } else { setPeoplePickerResults([]); @@ -83,9 +69,12 @@ const PilotForm: React.FC = ({ } }; - const onPersonSelected = (person: Person) => { - methods.setValue('name', person.displayName!.toString()); - setPeoplePickerValue(person.displayName!); + const onPersonSelected = (userPrincipalName: string) => { + const person: Person | undefined = peoplePickerResults.find((person) => person.userPrincipalName === userPrincipalName as string); + + methods.setValue('name', person?.displayName!); + methods.setValue('userId', person?.userPrincipalName!); + setPeoplePickerValue(person?.displayName!); setPeoplePickerResults([]) }; @@ -134,9 +123,7 @@ const PilotForm: React.FC = ({ ); const pilot = response.data; - setSelectedPerson({ - displayName: pilot.name - }); + setPeoplePickerValue(pilot.name); methods.reset(pilot); } catch (error) { console.log(error); @@ -175,15 +162,19 @@ const PilotForm: React.FC = ({
Name *
- + onPersonSelected(key as string)} + > + {peoplePickerResults.map((person: Person) => ( + + {person.displayName} + + ))} +
{isUserLoggedIn && <> @@ -244,19 +235,14 @@ const PilotForm: React.FC = ({ control={methods.control} rules={{ required: 'A state must be selected' }} render={({ field: { onChange, value } }) => ( - + selectedKeys={[value]} + > + {states.map((state) => ( + {state.label} + ))} + )} />
@@ -369,7 +355,7 @@ const PilotForm: React.FC = ({ ? isDisabled : false } - startContent={} + startContent={} onPress={onCancel} data-testid="pilot-cancel-button" > @@ -379,7 +365,7 @@ const PilotForm: React.FC = ({
{!state.isLoading && state.alert && ( -
+
dispatch({ type: 'SET_ALERT', payload: undefined }) @@ -259,7 +212,6 @@ const Pilots: React.FC = () => { )}
{state.pilots.length > 0 && screenSize !== ScreenSize.SM && - //
{(column) => ( @@ -285,7 +237,7 @@ const Pilots: React.FC = () => {
} {state.pilots.length > 0 && screenSize === ScreenSize.SM && - + }
@@ -299,7 +251,7 @@ const Pilots: React.FC = () => { )} {state.isConfirmDialogOpen && ( { - const [loading, setLoading] = useState(false); const [userPhoto, setUserPhoto] = useState(); - const [pages, setPages] = useState<{ name: string; path: string; }[]>([]); const appContext = useAppContext(); - const { isUserLoggedIn, login, logout } = useOidc() - const navigate = useNavigate(); + const { isUserLoggedIn, logout, login } = useOidc() const { pathname } = useLocation() - const getPages = () => { - const pages = [ - { - name: 'Flights', - path: '/' - }, - { - name: 'Logbook', - path: '/logbook' - }, - { - name: 'Pilots', - path: '/pilots' - } - ]; - - setPages(pages) - }; - const handleSignIn = () => { - // auth.signinRedirect(); - // auth.signinRedirect({ - // scope: `api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation` - // }) - login(); - // console.log(auth.user?.access_token) - }; - const handleSignOut = () => { - // auth.signoutRedirect(); - logout() - }; + const pages = [ + { + name: 'Flights', + path: '/' + }, + { + name: 'Logbook', + path: '/logbook' + }, + { + name: 'Pilots', + path: '/pilots' + } + ]; const getUserProfile = async (): Promise => { try { - const response: AxiosResponse = await httpClient.get(`api/user/profile`, { - // headers: { - // Authorization: `Bearer ${accessToken}` - // } - }); + const response: AxiosResponse = await httpClient.get(`api/msgraph/profile`); const userProfile: User = response.data; return userProfile; @@ -64,10 +40,7 @@ const SiteNav = () => { }; const getUserPhoto = async (): Promise => { try { - const response: AxiosResponse = await httpClient.get(`api/user/photo`, { - // headers: { - // Authorization: accessToken - // }, + const response: AxiosResponse = await httpClient.get(`api/msgraph/photo`, { responseType: 'arraybuffer' }); const arrayBufferView = new Uint8Array(response.data); @@ -79,82 +52,49 @@ const SiteNav = () => { throw new Error(); } }; - const handlePageClick = (url: string) => { - navigate(url); - }; - - // const Settings = () => { - // return ( - //
- // - // - // Sign Out - // - //
- // ); - // }; useEffect(() => { const setUserProfile = async () => { try { - setLoading(true); + const userProfile = await getUserProfile(); + const userPhoto = await getUserPhoto(); - // const userProfile = await getUserProfile(); - // const userPhoto = await getUserPhoto(); + setUserPhoto(userPhoto); - // setUserPhoto(userPhoto); - - // appContext.dispatch({ - // type: 'SET_USER_PROFILE', - // payload: userProfile - // }); - const oidc = await getOidc(); - - if (oidc.isUserLoggedIn) { - console.log((await oidc.getTokens()).accessToken) - } - + appContext.dispatch({ + type: 'SET_USER_PROFILE', + payload: userProfile + }); } catch (error) { console.log(error); - } finally { - setLoading(false); - } + } }; if ( isUserLoggedIn && Object.keys(appContext.state.userProfile).length === 0 ) { - console.log(isUserLoggedIn) - console.log() setUserProfile(); - getPages(); } }, [isUserLoggedIn]); - useEffect(() => { - getPages(); - }, []) - - useEffect(() => { - console.log(pathname) - }, [pathname]) - return ( - - - - + + + + + + - {pages.length > 0 && pages.map((page) => { + {pages.length > 0 && pages.map((page, index) => { return ( - + {page.name} @@ -163,9 +103,27 @@ const SiteNav = () => { })} - + {!isUserLoggedIn && + + } + {isUserLoggedIn && + + + + + + logout({redirectTo: 'specific url', url: '/'})} startContent={}> + Sign Out + + + + } ); diff --git a/client/src/components/stateSelect/StateSelect.tsx b/client/src/components/stateSelect/StateSelect.tsx new file mode 100644 index 0000000..e69de29 diff --git a/client/src/components/logTrackMaps/LogTrackMaps.css b/client/src/components/trackMap/TrackMap.css similarity index 100% rename from client/src/components/logTrackMaps/LogTrackMaps.css rename to client/src/components/trackMap/TrackMap.css diff --git a/client/src/components/logTrackMaps/LogTrackMaps.tsx b/client/src/components/trackMap/TrackMap.tsx similarity index 57% rename from client/src/components/logTrackMaps/LogTrackMaps.tsx rename to client/src/components/trackMap/TrackMap.tsx index 8eb4b3d..6d548ed 100644 --- a/client/src/components/logTrackMaps/LogTrackMaps.tsx +++ b/client/src/components/trackMap/TrackMap.tsx @@ -1,5 +1,5 @@ -import { useEffect, useState } from 'react'; -import { LogTrackMapsProps } from './LogTrackMapsProps.interface'; +import { Suspense, useEffect, useState } from 'react'; +import { TrackMapProps } from './TrackMapProps.interface'; import { AxiosInstance, AxiosResponse } from 'axios'; import { useAuth } from 'react-oidc-context' import { MapContainer, TileLayer } from 'react-leaflet'; @@ -7,16 +7,15 @@ import ReactLeafletKml from 'react-leaflet-kml'; import 'swiper/css'; import 'swiper/css/pagination'; import 'swiper/css'; -import './LogTrackMaps.css'; +import './TrackMap.css'; import 'leaflet/dist/leaflet.css'; import httpClient from '../../httpClient/httpClient' -const LogTrackMaps = ({ logId, tracks }: LogTrackMapsProps) => { +const TrackMap = ({ height, logId, tracks }: TrackMapProps) => { const [kmls, setKmls] = useState([]) - const auth = useAuth() + const auth = useAuth(); useEffect(() => { - console.log(tracks) const getTracks = async () => { const convertedTracks: any[] = [] @@ -24,12 +23,7 @@ const LogTrackMaps = ({ logId, tracks }: LogTrackMapsProps) => { const trackUrlSplit = track.url.split('/') const filename = trackUrlSplit[trackUrlSplit.length - 1]; const response: AxiosResponse = await httpClient.get( - `api/tracks/${logId}/${filename}`, - { - headers: { - Authorization: auth.user?.access_token - } - } + `api/tracks/${logId}/${filename}` ); const kml = new DOMParser().parseFromString(response.data, 'text/xml') @@ -40,24 +34,26 @@ const LogTrackMaps = ({ logId, tracks }: LogTrackMapsProps) => { } getTracks(); - }, [tracks]) + }, []) return ( - - {kmls.length > 0 && kmls.map((kml) => ( - - ))} + + + {kmls.length > 0 && kmls.map((kml) => ( + + ))} + ); } -export default LogTrackMaps; \ No newline at end of file +export default TrackMap; \ No newline at end of file diff --git a/client/src/components/logTrackMaps/LogTrackMapsProps.interface.ts b/client/src/components/trackMap/TrackMapProps.interface.ts similarity index 58% rename from client/src/components/logTrackMaps/LogTrackMapsProps.interface.ts rename to client/src/components/trackMap/TrackMapProps.interface.ts index acab4a0..a82d082 100644 --- a/client/src/components/logTrackMaps/LogTrackMapsProps.interface.ts +++ b/client/src/components/trackMap/TrackMapProps.interface.ts @@ -1,4 +1,5 @@ -export interface LogTrackMapsProps { +export interface TrackMapProps { + height: string; logId: string; tracks: {id: string; order: number; url: string}[]; } \ No newline at end of file diff --git a/client/src/components/tracksForm/TracksForm.tsx b/client/src/components/tracksForm/TracksForm.tsx index 6ae89df..f723a75 100644 --- a/client/src/components/tracksForm/TracksForm.tsx +++ b/client/src/components/tracksForm/TracksForm.tsx @@ -1,31 +1,44 @@ -import { useEffect, useReducer } from "react"; -// import { Button, Drawer, Icon, IconButton, IconName, Input, Loading } from "@noahspan/noahspan-components"; -import { AxiosError, AxiosInstance, AxiosResponse } from "axios"; +import { useEffect, useReducer, useRef } from "react"; +import { AxiosError, AxiosResponse } from "axios"; import { TracksFormProps } from "./TracksFormProps.interface"; import { FormMode } from "../../enums/formMode"; import { LogbookEntry } from "../logbook/LogbookEntry.interface"; import ConfirmationDialog from "../confirmationDialog/ConfirmationDialog"; import { initialState, reducer } from "./reducer"; -import LogTrackMaps from "../logTrackMaps/LogTrackMaps"; +import TrackMap from "../trackMap/TrackMap"; import httpClient from "../../httpClient/httpClient"; -import { Button, Drawer, DrawerContent, DrawerBody, DrawerHeader, Input, Spinner, DrawerFooter } from '@heroui/react'; +import { Button, Input, Spinner } from '@heroui/react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faUpload, faTrash, faXmark } from '@fortawesome/free-solid-svg-icons'; import { useLogbookContext } from "../../hooks/logbookContext/UseLogbookContext"; -import { Controller, useFieldArray, useFormContext } from "react-hook-form"; - const TracksForm = () => { const [state, dispatch] = useReducer(reducer, initialState) const logbookContext = useLogbookContext(); + const getTracks = async () => { + try { + const response: AxiosResponse = await httpClient.get( + `api/tracks/${logbookContext.state.selectedLogId}` + ) + + const tracks = response.data; + + dispatch({ type: 'SET_TRACKS', payload: tracks }) + } catch (error) { + const axiosError = error as AxiosError; + + logbookContext.dispatch({ type: 'SET_FORM_ALERT', payload: { severity: 'danger', message: axiosError.message }}) + } + } + const handleFileUpload = async (event: React.ChangeEvent) => { - console.log('blah') try { dispatch({ type: 'SET_IS_LOADING', payload: true}) const file = event.target.files![0] const formData = new FormData(); + const order = state.tracks.length + 1 formData.append('file', file); @@ -33,9 +46,10 @@ const TracksForm = () => { config.headers["Content-Type"] = 'multipart/form-data' return config - }) + }); - const uploadResponse: AxiosResponse = await httpClient.post(`api/tracks/${logbookContext.state.selectedLogId}/1`, formData); + await httpClient.post(`api/tracks/${logbookContext.state.selectedLogId}/${order}`, formData); + await getTracks(); // const uploadUrl = uploadResponse.data.url; // const tracks: string[] = log.tracks ? JSON.parse(log.tracks!) : []; @@ -53,49 +67,77 @@ const TracksForm = () => { } } - // const onDeleteTrack = async (index: number) => { - // dispatch({ type: 'SET_ON_DELETE_TRACK', payload: { isConfirmDialogOpen: true, selectedTrack: { index: index }}}) - // } + const onDeleteTrack = async (id: string, filename: string, index: number) => { + dispatch({ type: 'SET_ON_DELETE_TRACK', payload: { isConfirmDialogOpen: true, selectedTrack: { id: id, filename: filename, index: index }}}) + } + + const onConfirmDialogConfirm = async () => { + try { + await httpClient.delete(`api/tracks/${state.selectedTrack!.id}/${state.selectedTrack!.filename}/${logbookContext.state.selectedLogId}`); + + await getTracks(); + + // tracks.splice(state.selectedTrack!.index, 1); + // log.tracks = JSON.stringify(tracks); + // await httpClient.put(`api/logs/log/${selectedRowKey}`, log, config); + + // const updatedLog = await getLog(); + + // dispatch({ type: 'SET_TRACKS', payload: JSON.parse(updatedLog.tracks!) }) + dispatch({ type: 'SET_IS_CONFIRM_DIALOG_OPEN', payload: false }) + } catch (error) { + console.log(error); + } + } + + const onConfirmDialogCancel = async () => { + dispatch({ type: 'SET_IS_CONFIRM_DIALOG_OPEN', payload: false }) + } useEffect(() => { - const getTracks = async () => { - try { - const response: AxiosResponse = await httpClient.get( - `api/tracks/${logbookContext.state.selectedLogId}` - ) - - const tracks = response.data; - - dispatch({ type: 'SET_TRACKS', payload: tracks }) - } catch (error) { - const axiosError = error as AxiosError; - - logbookContext.dispatch({ type: 'SET_FORM_ALERT', payload: { severity: 'danger', message: axiosError.message }}) - } - } - if (logbookContext.state.selectedLogId) { getTracks(); } }, [logbookContext.state.selectedLogId]) + useEffect(() => { + console.log(logbookContext.state.formMode) + if (logbookContext.state.formMode === FormMode.VIEW) { + dispatch({ type: 'SET_IS_DISABLED', payload: true }); + } + }, [logbookContext.state.formMode]); + + useEffect(() => { + console.log(state.isDisabled) + }, [state.isDisabled]) + return (
+ {state.tracks.length > 0 && +
+ +
+ } <> - {state.tracks.length > 0 && - <> -
- -
-
- -
- - } + {state.tracks.length > 0 && state.tracks.map((track, index) => { + const filename: string = track.url.substring(track.url.lastIndexOf('/') + 1); + + return ( + <> +
+ +
+
+ +
+ + ) + })}
+ {state.isConfirmDialogOpen && ( + + )}
); @@ -136,31 +188,7 @@ const TracksForm = () => { // dispatch({ type: 'SET_ON_DELETE_TRACK', payload: { isConfirmDialogOpen: true, selectedTrack: { fileName: fileName, index: index }}}) // } - // const onConfirmDialogConfirm = async () => { - // try { - // const config = await getConfig(); - // await httpClient.delete(`api/logs/log/${selectedRowKey}/track?fileName=${state.selectedTrack!.fileName}`, config); - - // const log = await getLog(); - // const tracks: string[] = JSON.parse(log.tracks!); - - // tracks.splice(state.selectedTrack!.index, 1); - // log.tracks = JSON.stringify(tracks); - // await httpClient.put(`api/logs/log/${selectedRowKey}`, log, config); - - // const updatedLog = await getLog(); - - // dispatch({ type: 'SET_TRACKS', payload: JSON.parse(updatedLog.tracks!) }) - // dispatch({ type: 'SET_IS_CONFIRM_DIALOG_OPEN', payload: false }) - // } catch (error) { - // console.log(error); - // } - // } - - // const onConfirmDialogCancel = async () => { - // dispatch({ type: 'SET_IS_CONFIRM_DIALOG_OPEN', payload: false }) - // } // useEffect(() => { // const updateTracks = async () => { @@ -204,19 +232,8 @@ const TracksForm = () => { // })} // // } - {/* {logbookContext.state.formMode === FormMode.VIEW && - - } */} - {/* {state.isConfirmDialogOpen && ( - - )} */} + + //
// ) // } diff --git a/client/src/components/tracksForm/TracksFormState.interface.ts b/client/src/components/tracksForm/TracksFormState.interface.ts index 099237e..f1b86f2 100644 --- a/client/src/components/tracksForm/TracksFormState.interface.ts +++ b/client/src/components/tracksForm/TracksFormState.interface.ts @@ -1,9 +1,11 @@ export interface TracksFormState { isConfirmDialogOpen: boolean; isConfirmDialogLoading: boolean; + isDisabled: boolean; isLoading: boolean; selectedTrack: { - fileName: string, + id: string, + filename: string, index: number } | undefined; tracks: { id: string; order: number; url: string; }[]; diff --git a/client/src/components/tracksForm/reducer.ts b/client/src/components/tracksForm/reducer.ts index 7f754e5..83cfe03 100644 --- a/client/src/components/tracksForm/reducer.ts +++ b/client/src/components/tracksForm/reducer.ts @@ -3,13 +3,15 @@ import { TracksFormState } from "./TracksFormState.interface"; type Action = | { type: 'SET_IS_CONFIRM_DIALOG_OPEN'; payload: boolean } | { type: 'SET_IS_CONFORM_DIALOG_LOADING'; payload: boolean } + | { type: 'SET_IS_DISABLED'; payload: boolean } | { type: 'SET_IS_LOADING'; payload: boolean } - | { type: 'SET_ON_DELETE_TRACK'; payload: { isConfirmDialogOpen: boolean, selectedTrack: { fileName: string, index: number } }} + | { type: 'SET_ON_DELETE_TRACK'; payload: { isConfirmDialogOpen: boolean, selectedTrack: { id: string, filename: string, index: number } }} | { type: 'SET_TRACKS'; payload: { id: string; order: number; url: string }[] }; export const initialState: TracksFormState = { isConfirmDialogOpen: false, isConfirmDialogLoading: false, + isDisabled: false, isLoading: false, selectedTrack: undefined, tracks: [] @@ -29,6 +31,12 @@ export const reducer = (state: TracksFormState, action: Action): TracksFormState isConfirmDialogLoading: action.payload } } + case 'SET_IS_DISABLED': { + return { + ...state, + isDisabled: action.payload + } + } case 'SET_IS_LOADING': { return { ...state, diff --git a/client/src/hooks/featureFlag/UseFeatureFlag.tsx b/client/src/hooks/featureFlag/UseFeatureFlag.tsx deleted file mode 100644 index 002f929..0000000 --- a/client/src/hooks/featureFlag/UseFeatureFlag.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { useAppContext } from '../appContext/UseAppContext'; - -export const useFeatureFlag = (featureFlagKey: string) => { - const appContext = useAppContext(); - const featureFlag = appContext.state.featureFlags.find( - (featureFlag) => featureFlag.key === featureFlagKey - ); - console.log(featureFlag) - return featureFlag; -}; diff --git a/client/src/hooks/pilots/UsePilots.tsx b/client/src/hooks/pilots/UsePilots.tsx index 2f7b5e8..83dc340 100644 --- a/client/src/hooks/pilots/UsePilots.tsx +++ b/client/src/hooks/pilots/UsePilots.tsx @@ -23,7 +23,7 @@ export const usePilots = () => { const response: AxiosResponse = await httpClient.get( `/api/pilots` ); - console.log(response) + setPilots(response.data); } catch (error) { return error; diff --git a/client/src/main.tsx b/client/src/main.tsx index 126fde5..4777a71 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -7,15 +7,13 @@ import { BrowserRouter } from 'react-router-dom'; import { OidcProvider } from './auth/oidcConfig.ts'; ReactDOM.createRoot(document.getElementById('root')!).render( - - - - - - - - - - - + + + + + + + + + ); diff --git a/client/src/styles.css b/client/src/styles.css index f25ab6f..ce32b3c 100644 --- a/client/src/styles.css +++ b/client/src/styles.css @@ -5,6 +5,8 @@ @custom-variant dark (&:is(.dark *)); @plugin "@tailwindcss/typography"; -/* body { - @apply bg-base-300; -} */ +@theme inline { + --color-primary: #000000; +} + + diff --git a/database/flying.db b/database/flying.db index 00488c8f0c3693736627dc8554fc044181b1f1f7..dd824fd2ed9d80626050264b5fa8f5cc56471aef 100644 GIT binary patch delta 1323 zcma)+&uW~r61#&=YWf9fPKLa(cdwz65l7v)IvgxYV6kV2z!@p zv+d2FKTWMKi%dF~gAdc|&fY8%KnEU$@!-cMH2ib45Ik}q_v`OV@G11UNa%k>qu_E2 zeqEd`U7RhI=dufxY^_=@U7E}8t>jk4Lrgl8foqg!S}oUGG3~3}{z&_uGna$=-#|Vn zci;{E9Z4pWOnP(_uAS^9X}eC-Hd}kYsDCT^=CgMMS9m5pF#*@=yItULxsT{tv(@ps{c$+_ zH!eUpyzBXqpW_l#429se{|T4zZ$|IYXHs}cjvrs}l8ttv=yueVJ z@V3w;euRyz7Em8*&VRrkfkXZl|JUTfH4s!^^x2h2;+p(c?2p6_I z-mt@;o}3k?25?k%Ukr#A0a_%qFA>^fFjK3Y9?ytkECynGjW_|28>F^DGEVuwp5NIU*0VS4Z}zYpK@dLNEV%Vu8WaUP76815dxC@a7?=G5d7Evp zBerR_3%Hyg(KB?^@fK~NesEN%tJGE0;hEmQtoSG{5&;+4<-94qzNATovaHFK#f9ag zc+BSm>tp4GIaP0tOZU^w^Nykg1z-@}bi}$KR7pM#8Z@Ono?4Vk|wJU%AV$XBn0BtzNkUM zn@o*6g z*w2&0yB@^8`%&1_Q7_45x^?8Zb=!PwU7%7sT8mw9ce(71REN2_V(7d(0Bq=e_np04 zCyTetsJVwSUi3LUaE6+&klerkm^PwTPnH!;&a3jtQ*yN^KlEtbv`P<6hO1IV9>kWB zdJs@tZ!c&-6A5LmCTYq-Rjsrn%Z8^j=1ywQ1o?1KnGNzvRg>rB8b2{PGMbs3=8tBl z`OH*qd}Iupj%LSl_{+p?>ew;7@ie6(tD00<4DtnK4x2tWsmB%MPfNApyi_wLFbKPR zEbalctrBpx+2Q#DCiVauVSg69efF!52S=)krby+{jeujRAyE|K=~ybAh?|zT2zUz{ z{s!!uYuYN+eM8fzbBlwX#&0oY3GgY)it_R z*HDLNsq?>7#~9K2;lH4cF``9%Y;}wg!FQ&!CsoHz{9niZp*r^blc{4U$OHosmcfTK zT_@b1xZiO1v3J>%>=C-o2ADNw4WB~aSe?tYkV$}hE`8nWcWhKwGpIJZLkAoKCgnZh z*o}U6D*Lx)3#`vx)|vB#SB*-p6BfOBP3C+J3@*>Fzt?3bm~=ab$FR{wb8N$?O}E$I z{U@{xt`gZcQ4KwkEMc9PekAYdBe`LpPMXjamy@E~^fSGQem(|b+v1|)CNBCnh^;m9 Om