migrating to sqlite
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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],
|
||||
|
||||
5
api/src/interfaces/customJwtPayload.interface.ts
Normal file
5
api/src/interfaces/customJwtPayload.interface.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { JwtPayload } from "jwt-decode";
|
||||
|
||||
export interface CustomJwtPayload extends JwtPayload {
|
||||
roles: string[];
|
||||
}
|
||||
@@ -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<any> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -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<LogEntity> {
|
||||
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<LogEntity[]> {
|
||||
try {
|
||||
return await this.logService.findAll();
|
||||
@@ -53,7 +54,7 @@ export class LogController {
|
||||
}
|
||||
}
|
||||
|
||||
// @UseGuards(AuthGuard)
|
||||
|
||||
@Post()
|
||||
async create(@Body() logDto: LogDto): Promise<InsertResult> {
|
||||
try {
|
||||
@@ -65,22 +66,22 @@ export class LogController {
|
||||
}
|
||||
}
|
||||
|
||||
// @UseGuards(AuthGuard)
|
||||
@Put(':id')
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@Body() logDto: LogDto
|
||||
): Promise<UpdateResult> {
|
||||
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,
|
||||
|
||||
@@ -24,5 +24,5 @@ export class LogDto {
|
||||
solo?: number;
|
||||
pilotInCommand?: number;
|
||||
notes?: string;
|
||||
pilot?: PilotEntity;
|
||||
tracks?: []
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ export class LogEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string
|
||||
|
||||
@Column()
|
||||
pilotId: string;
|
||||
|
||||
@Column()
|
||||
date: Date;
|
||||
|
||||
|
||||
39
api/src/log/log.interceptor.ts
Normal file
39
api/src/log/log.interceptor.ts
Normal file
@@ -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<any> {
|
||||
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;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ export class LogService {
|
||||
async find(id: string): Promise<LogEntity> {
|
||||
const logEntity: LogEntity = await this.logRepository.findOne({
|
||||
where: { id: id },
|
||||
relations: ['pilot', 'tracks']
|
||||
// relations: ['pilot', 'tracks']
|
||||
});
|
||||
|
||||
return logEntity;
|
||||
|
||||
@@ -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<any> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
33
api/src/pilot/pilot.interceptor.ts
Normal file
33
api/src/pilot/pilot.interceptor.ts
Normal file
@@ -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<any> {
|
||||
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;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -39,9 +39,11 @@ export class TrackController {
|
||||
// }
|
||||
// }
|
||||
|
||||
@UseGuards(AuthGuard)
|
||||
@Get(':logId')
|
||||
async findAll(@Param('logId') logId: string): Promise<TrackEntity[]> {
|
||||
try {
|
||||
console.log('logId: ' + logId)
|
||||
return await this.trackService.findAll(logId);
|
||||
} catch (error) {
|
||||
const customError = error as CustomError;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 : <Navigate to='/' />
|
||||
}
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeroUIProvider navigate={navigate} useHref={useHref}>
|
||||
<SiteNav />
|
||||
<Routes>
|
||||
<Route path='/' element={<Flights />} />
|
||||
<Route path="/logbook" element={<Logbook />} />
|
||||
<Route path="/pilots" element={<Pilots />} />
|
||||
</Routes>
|
||||
</>
|
||||
</HeroUIProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
}));
|
||||
@@ -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 | HTMLElement>(
|
||||
null
|
||||
);
|
||||
const auth = useAuth();
|
||||
// const [anchorElAction, setAnchorElAction] = useState<null | HTMLElement>(
|
||||
// null
|
||||
// );
|
||||
// const auth = useAuth();
|
||||
|
||||
const onOpenActionMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorElAction(event.currentTarget);
|
||||
};
|
||||
// const onOpenActionMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||
// setAnchorElAction(event.currentTarget);
|
||||
// };
|
||||
|
||||
const onCloseActionMenu = () => {
|
||||
setAnchorElAction(null);
|
||||
};
|
||||
// const onCloseActionMenu = () => {
|
||||
// setAnchorElAction(null);
|
||||
// };
|
||||
|
||||
const options = [
|
||||
'Item 1',
|
||||
'Item 2'
|
||||
]
|
||||
|
||||
return (
|
||||
<></>
|
||||
<>
|
||||
<Dropdown
|
||||
onOptionSelected={() => console.log('clicked!')}
|
||||
options={options}
|
||||
>
|
||||
<IconButton>
|
||||
<Icon className='text-2xl' iconName={IconName.ELLIPSIS_VERTICAL} />
|
||||
</IconButton>
|
||||
</Dropdown>
|
||||
</>
|
||||
// <div>
|
||||
// <IconButton onClick={onOpenActionMenu}>
|
||||
// <Icon iconName={IconName.ELLIPSIS_VERTICAL} size="sm" />
|
||||
|
||||
@@ -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<ILogbookEntry[]>([]);
|
||||
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 (
|
||||
<div className='max-w-screen-lg mx-auto'>
|
||||
<div className='prose mt-5 mb-5'>
|
||||
<h1>Flights</h1>
|
||||
</div>
|
||||
{!isLoading &&
|
||||
{!logsLoading && state.alert && (
|
||||
<div>
|
||||
<LogbookCard logs={flights} mode='flights' />
|
||||
<Alert
|
||||
onClose={() =>
|
||||
dispatch({ type: 'SET_ALERT', payload: undefined })
|
||||
}
|
||||
color={state.alert.severity}
|
||||
title={state.alert.message}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!logsLoading &&
|
||||
<div>
|
||||
<LogbookCard logs={state.flights} mode='flights' />
|
||||
</div>
|
||||
}
|
||||
{!isLoading && [...Array(6)].map((_element, index) => {
|
||||
{logsLoading && [...Array(6)].map((_element, index) => {
|
||||
return (
|
||||
<div className='mb-5'>
|
||||
<Card
|
||||
|
||||
8
client/src/components/flights/FlightsState.interface.ts
Normal file
8
client/src/components/flights/FlightsState.interface.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Alert } from "../../interfaces/Alert.interface";
|
||||
import { LogbookEntry } from "../logbook/LogbookEntry.interface";
|
||||
|
||||
export interface FlightsState {
|
||||
alert: Alert | undefined;
|
||||
flights: LogbookEntry[];
|
||||
isLoading: boolean;
|
||||
}
|
||||
44
client/src/components/flights/reducer.tsx
Normal file
44
client/src/components/flights/reducer.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Alert } from "../../interfaces/Alert.interface";
|
||||
import { LogbookEntry } from "../logbook/LogbookEntry.interface";
|
||||
import { FlightsState } from "./FlightsState.interface";
|
||||
|
||||
|
||||
type Action =
|
||||
| { type: 'SET_ALERT'; payload: Alert | undefined }
|
||||
| { type: 'SET_FLIGHTS'; payload: LogbookEntry[] }
|
||||
| { type: 'SET_IS_LOADING'; payload: boolean }
|
||||
|
||||
export const initialState: FlightsState = {
|
||||
alert: undefined,
|
||||
flights: [],
|
||||
isLoading: true
|
||||
}
|
||||
|
||||
export const reducer = (
|
||||
state: FlightsState,
|
||||
action: Action
|
||||
): FlightsState => {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { FormMode } from '../../enums/formMode';
|
||||
|
||||
export interface ILogFormProps {
|
||||
logId?: string;
|
||||
isDrawerOpen: boolean;
|
||||
mode: FormMode;
|
||||
onOpenClose: (mode: FormMode) => void;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
8
client/src/components/logForm/LogFormProps.interface.ts
Normal file
8
client/src/components/logForm/LogFormProps.interface.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { FormMode } from '../../enums/formMode';
|
||||
|
||||
export interface LogFormProps {
|
||||
logId?: string;
|
||||
// isDrawerOpen: boolean;
|
||||
mode: FormMode;
|
||||
// onOpenClose: (mode: FormMode) => void;
|
||||
}
|
||||
13
client/src/components/logForm/LogFormState.interface.ts
Normal file
13
client/src/components/logForm/LogFormState.interface.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Selection } from "@heroui/react";
|
||||
import { Alert } from "../../interfaces/Alert.interface";
|
||||
|
||||
export interface LogFormState {
|
||||
alert: Alert | undefined;
|
||||
experienceSelectedKeys: Selection;
|
||||
instrumentSelectedKeys: Selection;
|
||||
isDisabled: boolean;
|
||||
isLoading: boolean;
|
||||
landingsSelectedKeys: Selection;
|
||||
pilotOptions: { key: string; label: string; }[];
|
||||
selectedPilotName: string;
|
||||
}
|
||||
@@ -1,31 +1,32 @@
|
||||
import { Alert } from '../../interfaces/Alert.interface';
|
||||
import { ILogFormState } from './ILogFormState';
|
||||
import { LogFormState } from './LogFormState.interface';
|
||||
import { Selection } from '@heroui/react';
|
||||
|
||||
type Action =
|
||||
| { type: 'SET_ALERT'; payload: Alert | undefined }
|
||||
| { type: 'SET_EXPERIENCE_COLLAPSE_OPEN'; payload: boolean }
|
||||
| { type: 'SET_INSTRUMENT_COLLAPSE_OPEN'; payload: boolean }
|
||||
| { type: 'SET_EXPERIENCE_SELECTED_KEYS'; payload: Selection }
|
||||
| { type: 'SET_INSTRUMENT_SELECTED_KEYS'; payload: Selection }
|
||||
| { type: 'SET_IS_DISABLED'; payload: boolean }
|
||||
| { type: 'SET_IS_LOADING'; payload: boolean }
|
||||
| { type: 'SET_LANDINGS_COLLAPSE_OPEN'; payload: boolean }
|
||||
| { type: 'SET_PILOT_OPTIONS'; payload: { label: string; value: string }[] }
|
||||
| { type: 'SET_LANDINGS_SELECTED_KEYS'; payload: Selection }
|
||||
| { type: 'SET_PILOT_OPTIONS'; payload: { key: string, label: string; }[] }
|
||||
| { type: 'SET_SELECTED_PILOT_NAME'; payload: string };
|
||||
|
||||
export const initialState: ILogFormState = {
|
||||
export const initialState: LogFormState = {
|
||||
alert: undefined,
|
||||
experienceCollapseOpen: true,
|
||||
instrumentCollapseOpen: false,
|
||||
experienceSelectedKeys: new Set([]),
|
||||
instrumentSelectedKeys: new Set([]),
|
||||
isDisabled: false,
|
||||
isLoading: true,
|
||||
landingsCollapseOpen: true,
|
||||
landingsSelectedKeys: new Set(['1']),
|
||||
pilotOptions: [],
|
||||
selectedPilotName: ''
|
||||
};
|
||||
|
||||
export const reducer = (
|
||||
state: ILogFormState,
|
||||
state: LogFormState,
|
||||
action: Action
|
||||
): ILogFormState => {
|
||||
): LogFormState => {
|
||||
switch (action.type) {
|
||||
case 'SET_ALERT': {
|
||||
return {
|
||||
@@ -33,10 +34,10 @@ export const reducer = (
|
||||
alert: action.payload
|
||||
};
|
||||
}
|
||||
case 'SET_EXPERIENCE_COLLAPSE_OPEN': {
|
||||
case 'SET_EXPERIENCE_SELECTED_KEYS': {
|
||||
return {
|
||||
...state,
|
||||
experienceCollapseOpen: action.payload
|
||||
experienceSelectedKeys: action.payload
|
||||
}
|
||||
}
|
||||
case 'SET_IS_DISABLED': {
|
||||
@@ -45,10 +46,10 @@ export const reducer = (
|
||||
isDisabled: action.payload
|
||||
};
|
||||
}
|
||||
case 'SET_INSTRUMENT_COLLAPSE_OPEN': {
|
||||
case 'SET_INSTRUMENT_SELECTED_KEYS': {
|
||||
return {
|
||||
...state,
|
||||
instrumentCollapseOpen: action.payload
|
||||
instrumentSelectedKeys: action.payload
|
||||
}
|
||||
}
|
||||
case 'SET_IS_LOADING': {
|
||||
@@ -57,10 +58,10 @@ export const reducer = (
|
||||
isLoading: action.payload
|
||||
};
|
||||
}
|
||||
case 'SET_LANDINGS_COLLAPSE_OPEN': {
|
||||
case 'SET_LANDINGS_SELECTED_KEYS': {
|
||||
return {
|
||||
...state,
|
||||
landingsCollapseOpen: action.payload
|
||||
landingsSelectedKeys: action.payload
|
||||
}
|
||||
}
|
||||
case 'SET_PILOT_OPTIONS': {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { LogTrackMapsProps } from './LogTrackMapsProps.interface';
|
||||
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
||||
import { AxiosInstance, AxiosResponse } from 'axios';
|
||||
import { useAuth } from 'react-oidc-context'
|
||||
import { MapContainer, TileLayer } from 'react-leaflet';
|
||||
@@ -10,10 +9,10 @@ import 'swiper/css/pagination';
|
||||
import 'swiper/css';
|
||||
import './LogTrackMaps.css';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import httpClient from '../../httpClient/httpClient'
|
||||
|
||||
const LogTrackMaps = ({ logId, tracks }: LogTrackMapsProps) => {
|
||||
const [kmls, setKmls] = useState<any[]>([])
|
||||
const httpClient: AxiosInstance = useHttpClient();
|
||||
const auth = useAuth()
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
import { useEffect, useReducer } from "react";
|
||||
import { Button, Drawer, Icon, IconButton, IconName, Input, Loading } from "@noahspan/noahspan-components";
|
||||
import { useHttpClient } from "../../hooks/httpClient/UseHttpClient";
|
||||
import { AxiosInstance, AxiosResponse } from "axios";
|
||||
import { LogTracksProps } from "./LogTracksProps.interface";
|
||||
import { FormMode } from "../../enums/formMode";
|
||||
import { ILogbookEntry } from "../logbook/ILogbookEntry";
|
||||
import ConfirmationDialog from "../confirmationDialog/ConfirmationDialog";
|
||||
import { initialState, reducer } from "./reducer";
|
||||
import LogTrackMaps from "../logTrackMaps/LogTrackMaps";
|
||||
import { useAuth } from "react-oidc-context";
|
||||
|
||||
const LogTracks = ({ isDrawerOpen, mode, onOpenClose, selectedLogId }: LogTracksProps) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState)
|
||||
const httpClient: AxiosInstance = useHttpClient();
|
||||
const auth = useAuth();
|
||||
|
||||
const getConfig = async () => {
|
||||
const config = auth.isAuthenticated
|
||||
? { headers: { Authorization: auth.user?.access_token } }
|
||||
: {};
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// const getLog = async (): Promise<ILogbookEntry> => {
|
||||
// const logResponse: AxiosResponse = await httpClient.get(
|
||||
// `api/tracks/${selectedLogId}`,
|
||||
// await getConfig()
|
||||
// );
|
||||
// const logData: ILogbookEntry = logResponse.data;
|
||||
|
||||
// return logData
|
||||
// }
|
||||
|
||||
// const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>, order: number) => {
|
||||
// try {
|
||||
// dispatch({ type: 'SET_IS_LOADING', payload: true})
|
||||
|
||||
// const file = event.target.files![0]
|
||||
// const formData = new FormData();
|
||||
// const config = await getConfig();
|
||||
// const formDataConfig = {
|
||||
// headers: {
|
||||
// ...config.headers,
|
||||
// 'Content-Type': 'multipart/form-data'
|
||||
// }
|
||||
// }
|
||||
|
||||
// formData.append('file', file);
|
||||
|
||||
// const uploadResponse: AxiosResponse = await httpClient.post(`api/tracks/${selectedLogId}/${order}`, formData, formDataConfig);
|
||||
// const uploadUrl = uploadResponse.data.url;
|
||||
// const tracks: string[] = log.tracks ? JSON.parse(log.tracks!) : [];
|
||||
|
||||
// tracks.push(uploadUrl)
|
||||
// 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!) })
|
||||
// } catch (error) {
|
||||
// console.log(error)
|
||||
// } finally {
|
||||
// dispatch({ type: 'SET_IS_LOADING', payload: false });
|
||||
// }
|
||||
// }
|
||||
|
||||
const onCancel = () => {
|
||||
onOpenClose(FormMode.CANCEL)
|
||||
}
|
||||
|
||||
const onDeleteTrack = async (fileName: string, index: number) => {
|
||||
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 () => {
|
||||
// const log = await getLog();
|
||||
|
||||
// dispatch({ type: 'SET_TRACKS', payload: log.tracks! });
|
||||
// }
|
||||
|
||||
// updateTracks();
|
||||
// }, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (isDrawerOpen) {
|
||||
console.log(selectedLogId)
|
||||
const getTracks = async () => {
|
||||
const tracks = await httpClient.get(`api/tracks/${selectedLogId}`);
|
||||
|
||||
console.log(tracks);
|
||||
}
|
||||
|
||||
getTracks();
|
||||
}
|
||||
}, [isDrawerOpen])
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
open={isDrawerOpen}
|
||||
position='right'
|
||||
width='25%'
|
||||
>
|
||||
<div>
|
||||
<div>
|
||||
<h4>{`${mode.toString().toLowerCase().charAt(0).toUpperCase() + mode.toString().slice(1).toLowerCase()} Tracks`}</h4>
|
||||
</div>
|
||||
<div>
|
||||
<IconButton disabled={state.isLoading ? true : false} onClick={onCancel}>
|
||||
<Icon iconName={IconName.XMARK} />
|
||||
</IconButton>
|
||||
</div>
|
||||
{mode === FormMode.EDIT &&
|
||||
<>
|
||||
{state.isLoading &&
|
||||
<>
|
||||
<div>
|
||||
<Loading size='xl' />
|
||||
</div>
|
||||
<div>
|
||||
Loading...
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
{!state.isLoading && state.tracks.length > 0 && state.tracks.map((track, index) => {
|
||||
const trackSplit = track.url.split('/')
|
||||
const filename = trackSplit[trackSplit.length - 1];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<Input disabled={true} value={filename} />
|
||||
</div>
|
||||
<div>
|
||||
<IconButton onClick={() => onDeleteTrack(filename, index)}><Icon iconName={IconName.TRASH} /></IconButton>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
})}
|
||||
<div>
|
||||
<Button
|
||||
disabled={state.isLoading ? true : false}
|
||||
startContent={<Icon iconName={IconName.XMARK} />}
|
||||
onClick={onCancel}
|
||||
size='sm'
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
{mode.toString() !== FormMode.VIEW && (
|
||||
<Button
|
||||
disabled={state.isLoading ? true : false}
|
||||
startContent={<Icon iconName={IconName.UPLOAD} />}
|
||||
>
|
||||
Upload Track
|
||||
{/* <input hidden onChange={handleFileUpload} type='file' /> */}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
{mode === FormMode.VIEW &&
|
||||
<LogTrackMaps logId={selectedLogId!} tracks={state.tracks} />
|
||||
}
|
||||
</div>
|
||||
{/* {state.isConfirmDialogOpen && (
|
||||
<ConfirmationDialog
|
||||
contentText="Are you sure you want to delete this track?"
|
||||
isLoading={state.isConfirmDialogLoading}
|
||||
isOpen={state.isConfirmDialogOpen}
|
||||
onCancel={onConfirmDialogCancel}
|
||||
onConfirm={onConfirmDialogConfirm}
|
||||
title="Confirm Delete"
|
||||
/>
|
||||
)} */}
|
||||
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
export default LogTracks;
|
||||
@@ -1,65 +1,374 @@
|
||||
import { useEffect, useReducer } from 'react';
|
||||
import { Key, useEffect, useReducer } from 'react';
|
||||
import LogForm from '../logForm/LogForm';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
ColumnDef,
|
||||
Icon,
|
||||
IconButton,
|
||||
IconName,
|
||||
Loading,
|
||||
Table
|
||||
} from '@noahspan/noahspan-components';
|
||||
import { initialState, reducer } from './reducer';
|
||||
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
||||
import { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { FormMode } from '../../enums/formMode';
|
||||
import { authColumns, unauthColumns } from './columns';
|
||||
import ActionMenu from '../actionMenu/ActionMenu';
|
||||
import ConfirmationDialog from '../confirmationDialog/ConfirmationDialog';
|
||||
import { ILogbookEntry } from './ILogbookEntry';
|
||||
import { LogbookEntry } from './LogbookEntry.interface';
|
||||
import LogbookCard from '../logbookCard/LogbookCard';
|
||||
import LogTracks from '../logTracks/LogTracks';
|
||||
import { useOidc } from '../../auth/oidcConfig';
|
||||
import httpClient from '../../httpClient/httpClient';
|
||||
import { useUserRole } from '../../hooks/userRole/UseUserRole';
|
||||
import { UserRole } from '../../enums/userRole';
|
||||
import { useBreakpoints } from '../../hooks/useBreakpoints/UseBreakpoints';
|
||||
import { ScreenSize } from '../../enums/screenSize';
|
||||
import { Table, TableHeader, TableBody, TableColumn, Dropdown, DropdownTrigger, Button, DropdownSection, DropdownMenu, DropdownItem, Alert, TableRow, TableCell } from '@heroui/react';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faAdd, faEllipsisVertical, faPen, faEye, faTrash, faMapLocationDot } from '@fortawesome/free-solid-svg-icons'
|
||||
import { CellContext, ColumnDef, flexRender, getCoreRowModel, getPaginationRowModel, HeaderContext, useReactTable } from '@tanstack/react-table';
|
||||
import { useLogbookContext } from '../../hooks/logbookContext/UseLogbookContext';
|
||||
import LogbookDrawer from '../logbookDrawer/LogbookDrawer';
|
||||
|
||||
const Logbook: React.FC<unknown> = () => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const httpClient: AxiosInstance = useHttpClient();
|
||||
const { isUserLoggedIn } = useOidc()
|
||||
const actionsColumn: ColumnDef<ILogbookEntry> = {
|
||||
const logbookContext = useLogbookContext()
|
||||
const { isUserLoggedIn } = useOidc();
|
||||
const { userRole } = useUserRole();
|
||||
const { screenSize } = useBreakpoints();
|
||||
const columnTotal = (info: HeaderContext<LogbookEntry, unknown>): number => {
|
||||
const blah = info.table
|
||||
const values: number[] = info.table.getPaginationRowModel().rows.map((row: any) => Number(row.getValue(info.column.id))).filter((value: any) => !Number.isNaN(value));
|
||||
let total: number = 0;
|
||||
|
||||
if (values.length > 0) {
|
||||
total = values.reduce((accumulator, currentValue) => accumulator + currentValue, total)
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
const pilotName: ColumnDef<LogbookEntry> = {
|
||||
id: 'pilotName',
|
||||
accessorKey: 'pilot',
|
||||
header: 'Pilot',
|
||||
footer: 'PAGE TOTALS',
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) => {
|
||||
const pilot: any = info.getValue();
|
||||
|
||||
return pilot.name
|
||||
}
|
||||
}
|
||||
const date: ColumnDef<LogbookEntry> = {
|
||||
id: 'date',
|
||||
accessorKey: 'date',
|
||||
header: 'Date',
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) => {
|
||||
const date = new Date(info.getValue() as string);
|
||||
const formattedDate = `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`
|
||||
|
||||
return formattedDate;
|
||||
}
|
||||
}
|
||||
const aircraftMakeModel: ColumnDef<LogbookEntry> = {
|
||||
id: 'aircraftMakeModel',
|
||||
accessorKey: 'aircraftMakeModel',
|
||||
header: 'Aircraft Make & Model'
|
||||
}
|
||||
const route: ColumnDef<LogbookEntry> = {
|
||||
id: 'route',
|
||||
header: 'Route of Flight',
|
||||
meta: {
|
||||
headerAlign: 'center'
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
id: 'routeFrom',
|
||||
accessorKey: 'routeFrom',
|
||||
header: 'From'
|
||||
},
|
||||
{
|
||||
id: 'routeTo',
|
||||
accessorKey: 'routeTo',
|
||||
header: 'To'
|
||||
}
|
||||
]
|
||||
}
|
||||
const durationOfFlight: ColumnDef<LogbookEntry> = {
|
||||
id: 'durationOfFlight',
|
||||
accessorKey: 'durationOfFlight',
|
||||
header: 'Duration Of Flight',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
},
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) =>
|
||||
info.getValue() ? parseFloat(info.getValue() as string).toFixed(1) : '',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : ''
|
||||
}
|
||||
const notes: ColumnDef<LogbookEntry> = {
|
||||
id: 'notes',
|
||||
accessorKey: 'notes',
|
||||
header: 'Notes'
|
||||
}
|
||||
const actions: ColumnDef<LogbookEntry> = {
|
||||
id: 'actions',
|
||||
header: 'Actions',
|
||||
meta: {
|
||||
align: 'center',
|
||||
headerAlign: 'center'
|
||||
},
|
||||
cell: (info: any) => (
|
||||
<ActionMenu
|
||||
id={info.row.original.id}
|
||||
onDelete={onDeleteLog}
|
||||
onOpenCloseForm={onOpenCloseLogForm}
|
||||
onOpenCloseTracks={onOpenCloseTracks}
|
||||
/>
|
||||
)
|
||||
}
|
||||
const tracksColumn: ColumnDef<ILogbookEntry> = {
|
||||
accessorKey: 'tracks',
|
||||
header: 'Tracks',
|
||||
cell: (info: any) => {
|
||||
if (info.row.original.tracks.length > 0) {
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) => {
|
||||
return (
|
||||
<IconButton onClick={() => onOpenCloseTracks(FormMode.VIEW, info.row.original.rowKey)}><Icon iconName={IconName.MAP_LOCATION_DOT} /></IconButton>
|
||||
<Dropdown>
|
||||
<DropdownTrigger>
|
||||
<Button isIconOnly variant='light' size='lg'>
|
||||
<FontAwesomeIcon icon={faEllipsisVertical} />
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu>
|
||||
<DropdownSection showDivider>
|
||||
<DropdownItem
|
||||
key='edit'
|
||||
onPress={() => onOpenCloseDrawer(FormMode.EDIT, info.row.original.id)}
|
||||
startContent={<FontAwesomeIcon icon={faPen} />}
|
||||
>
|
||||
Edit
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
key='view'
|
||||
onPress={() => onOpenCloseDrawer(FormMode.VIEW, info.row.original.id)}
|
||||
startContent={<FontAwesomeIcon icon={faEye} />}
|
||||
>
|
||||
View
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
key='tracks'
|
||||
onPress={() => onOpenCloseDrawer(FormMode.EDIT, info.row.original.id)}
|
||||
startContent={<FontAwesomeIcon icon={faMapLocationDot} />}
|
||||
>
|
||||
Tracks
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
<DropdownSection>
|
||||
<DropdownItem
|
||||
key='Delete'
|
||||
startContent={<FontAwesomeIcon icon={faTrash} />}
|
||||
>
|
||||
Delete
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const unauthColumns: ColumnDef<LogbookEntry>[] = [
|
||||
pilotName,
|
||||
date,
|
||||
aircraftMakeModel,
|
||||
route,
|
||||
durationOfFlight,
|
||||
notes
|
||||
]
|
||||
|
||||
const authColumns: ColumnDef<LogbookEntry>[] = [
|
||||
pilotName,
|
||||
date,
|
||||
aircraftMakeModel,
|
||||
{
|
||||
id: 'aircraftIdentity',
|
||||
accessorKey: 'aircraftIdentity',
|
||||
header: 'Aircraft Identity',
|
||||
},
|
||||
route,
|
||||
durationOfFlight,
|
||||
{
|
||||
id: 'singleEngineLand',
|
||||
accessorKey: 'singleEngineLand',
|
||||
header: 'Single Engine Land',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
},
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) =>
|
||||
info.getValue() ? parseFloat(info.getValue() as string).toFixed(1) : '',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : ''
|
||||
},
|
||||
{
|
||||
id: 'landings',
|
||||
header: 'Landings',
|
||||
meta: {
|
||||
headerAlign: 'center'
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
id: 'landingsDay',
|
||||
accessorKey: 'landingsDay',
|
||||
header: 'Day',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'landingsNight',
|
||||
accessorKey: 'landingsNight',
|
||||
header: 'Night',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'instrument',
|
||||
header: 'Instrument',
|
||||
meta: {
|
||||
headerAlign: 'center'
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
id: 'instrumentActual',
|
||||
accessorKey: 'instrumentActual',
|
||||
header: 'Actual',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
},
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) =>
|
||||
info.getValue() ? parseFloat(info.getValue() as string).toFixed(1) : '',
|
||||
},
|
||||
{
|
||||
id: 'instrumentSimulated',
|
||||
accessorKey: 'instrumentSimulated',
|
||||
header: 'Simulated',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
},
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) =>
|
||||
info.getValue() ? parseFloat(info.getValue() as string).toFixed(1) : ''
|
||||
},
|
||||
{
|
||||
id: 'instrumentApproaches',
|
||||
accessorKey: 'instrumentApproaches',
|
||||
header: 'Approaches',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'instrumentHolds',
|
||||
accessorKey: 'instrumentHolds',
|
||||
header: 'Holds',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'instrumentNavTrack',
|
||||
accessorKey: 'instrumentNavTrack',
|
||||
header: 'Nav/Track',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'experienceTraining',
|
||||
header: 'Type of pilot experience or training',
|
||||
meta: {
|
||||
headerAlign: 'center'
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
id: 'groundTrainingReceived',
|
||||
accessorKey: 'groundTrainingReceived',
|
||||
header: 'Ground Training Received',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
},
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) =>
|
||||
info.getValue() ? parseFloat(info.getValue() as string).toFixed(1) : ''
|
||||
},
|
||||
{
|
||||
id: 'flightTrainingReceived',
|
||||
accessorKey: 'flightTrainingReceived',
|
||||
header: 'Flight Training Received',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
},
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) =>
|
||||
info.getValue() ? parseFloat(info.getValue() as string).toFixed(1) : ''
|
||||
},
|
||||
{
|
||||
id: 'crossCountry',
|
||||
accessorKey: 'crossCountry',
|
||||
header: 'Cross Country',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
},
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) =>
|
||||
info.getValue() ? parseFloat(info.getValue() as string).toFixed(1) : ''
|
||||
},
|
||||
{
|
||||
id: 'night',
|
||||
accessorKey: 'night',
|
||||
header: 'Night',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
},
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) =>
|
||||
info.getValue() ? parseFloat(info.getValue() as string).toFixed(1) : ''
|
||||
},
|
||||
{
|
||||
id: 'solo',
|
||||
accessorKey: 'solo',
|
||||
header: 'Solo',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
},
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) =>
|
||||
info.getValue() ? parseFloat(info.getValue() as string).toFixed(1) : ''
|
||||
},
|
||||
{
|
||||
id: 'pilotInCommand',
|
||||
accessorKey: 'pilotInCommand',
|
||||
header: 'Pilot In Command',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
},
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) =>
|
||||
info.getValue() ? parseFloat(info.getValue() as string).toFixed(1) : ''
|
||||
}
|
||||
]
|
||||
},
|
||||
notes,
|
||||
actions
|
||||
]
|
||||
|
||||
const getLogbookEntries = async () => {
|
||||
try {
|
||||
dispatch({ type: 'SET_IS_LOADING', payload: true });
|
||||
|
||||
const response: AxiosResponse = await httpClient.get(`api/logs`);
|
||||
const entries: ILogbookEntry[] = response.data;
|
||||
console.log(entries)
|
||||
const entries: LogbookEntry[] = response.data;
|
||||
entries.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
|
||||
|
||||
if (response.data.length > 0) {
|
||||
@@ -69,42 +378,43 @@ const Logbook: React.FC<unknown> = () => {
|
||||
dispatch({ type: 'SET_ALERT', payload: undefined})
|
||||
}
|
||||
} else {
|
||||
dispatch({ type: 'SET_ALERT', payload: { severity: 'info', message: 'No logbook entries found.'}})
|
||||
dispatch({ type: 'SET_ALERT', payload: { severity: 'default', message: 'No logbook entries found.'}})
|
||||
}
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError;
|
||||
|
||||
dispatch({
|
||||
type: 'SET_ALERT',
|
||||
payload: { severity: 'error', message: `Loading of logbook entries failed with the following message: ${axiosError.message}`}
|
||||
payload: { severity: 'danger', message: `Loading of logbook entries failed with the following message: ${axiosError.message}`}
|
||||
});
|
||||
} finally {
|
||||
dispatch({ type: 'SET_IS_LOADING', payload: false });
|
||||
}
|
||||
};
|
||||
|
||||
const onOpenCloseLogForm = (mode: FormMode, logId?: string) => {
|
||||
const onOpenCloseDrawer= (mode: FormMode, logId?: string) => {
|
||||
console.log(logId)
|
||||
switch (mode) {
|
||||
case FormMode.ADD:
|
||||
case FormMode.EDIT:
|
||||
case FormMode.VIEW:
|
||||
dispatch({
|
||||
type: 'SET_OPEN_CLOSE_LOG_FORM',
|
||||
logbookContext.dispatch({
|
||||
type: 'SET_OPEN_CLOSE_DRAWER',
|
||||
payload: {
|
||||
formMode: mode,
|
||||
selectedLogId: logId,
|
||||
isFormOpen: true
|
||||
selectedLogId: logId!,
|
||||
isDrawerOpen: true
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
case FormMode.CANCEL:
|
||||
dispatch({
|
||||
type: 'SET_OPEN_CLOSE_LOG_FORM',
|
||||
logbookContext.dispatch({
|
||||
type: 'SET_OPEN_CLOSE_DRAWER',
|
||||
payload: {
|
||||
formMode: mode,
|
||||
selectedLogId: undefined,
|
||||
isFormOpen: false
|
||||
isDrawerOpen: false
|
||||
}
|
||||
});
|
||||
|
||||
@@ -112,34 +422,6 @@ const Logbook: React.FC<unknown> = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const onOpenCloseTracks = (mode: FormMode, rowKey?: string) => {
|
||||
switch(mode) {
|
||||
case FormMode.EDIT:
|
||||
case FormMode.VIEW:
|
||||
dispatch({
|
||||
type: 'SET_OPEN_CLOSE_TRACKS',
|
||||
payload: {
|
||||
tracksMode: mode,
|
||||
isTracksOpen: true,
|
||||
selectedRowKey: rowKey
|
||||
}
|
||||
})
|
||||
|
||||
break;
|
||||
case FormMode.CANCEL:
|
||||
dispatch({
|
||||
type: 'SET_OPEN_CLOSE_TRACKS',
|
||||
payload: {
|
||||
tracksMode: mode,
|
||||
isTracksOpen: false,
|
||||
selectedRowKey: undefined
|
||||
}
|
||||
})
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const onDeleteLog = (logId: string) => {
|
||||
dispatch({
|
||||
type: 'SET_DELETE',
|
||||
@@ -151,7 +433,7 @@ const Logbook: React.FC<unknown> = () => {
|
||||
try {
|
||||
dispatch({ type: 'SET_IS_CONFIRMATION_DIALOG_LOADING', payload: true });
|
||||
|
||||
await httpClient.delete(`api/logs/${state.selectedLogId}`);
|
||||
await httpClient.delete(`api/logs/${logbookContext.state.selectedLogId}`);
|
||||
|
||||
dispatch({
|
||||
type: 'SET_DELETE',
|
||||
@@ -163,7 +445,7 @@ const Logbook: React.FC<unknown> = () => {
|
||||
|
||||
dispatch({
|
||||
type: 'SET_ALERT',
|
||||
payload: { severity: 'error', message: `Loading of logbook entries failed with the following message: ${axiosError.message}`}
|
||||
payload: { severity: 'danger', message: `Loading of logbook entries failed with the following message: ${axiosError.message}`}
|
||||
});
|
||||
} finally {
|
||||
dispatch({ type: 'SET_IS_CONFIRMATION_DIALOG_LOADING', payload: false });
|
||||
@@ -177,36 +459,49 @@ const Logbook: React.FC<unknown> = () => {
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let newColumns: ColumnDef<ILogbookEntry>[];
|
||||
// useEffect(() => {
|
||||
// let newColumns: ColumnDef<LogbookEntry>[];
|
||||
|
||||
if (isUserLoggedIn) {
|
||||
newColumns = [...authColumns];
|
||||
} else {
|
||||
newColumns = [...unauthColumns];
|
||||
}
|
||||
// if (userRole === UserRole.WRITE) {
|
||||
// newColumns = [...authColumns];
|
||||
// } else {
|
||||
// newColumns = [...unauthColumns];
|
||||
// }
|
||||
|
||||
const actionsColumnExists = newColumns.find((column) => column.id === 'actions');
|
||||
const tracksColumnExists = newColumns.find((column) => column.id === 'actions');
|
||||
// const actionsColumnExists = newColumns.find((column) => column.id === 'actions');
|
||||
// const tracksColumnExists = newColumns.find((column) => column.id === 'actions');
|
||||
|
||||
if (!actionsColumnExists) {
|
||||
newColumns.push(actionsColumn);
|
||||
}
|
||||
// if (!actionsColumnExists) {
|
||||
// newColumns.push(actionsColumn);
|
||||
// }
|
||||
|
||||
if (!tracksColumnExists) {
|
||||
const notesColumnIndex = newColumns.findIndex((column) => column.id === 'notes')
|
||||
// if (!tracksColumnExists) {
|
||||
// const notesColumnIndex = newColumns.findIndex((column) => column.id === 'notes')
|
||||
|
||||
newColumns.splice(notesColumnIndex, 0, tracksColumn)
|
||||
}
|
||||
// newColumns.splice(notesColumnIndex, 0, tracksColumn)
|
||||
// }
|
||||
|
||||
dispatch({ type: 'SET_COLUMNS', payload: newColumns })
|
||||
}, [isUserLoggedIn])
|
||||
// dispatch({ type: 'SET_COLUMNS', payload: newColumns })
|
||||
// }, [isUserLoggedIn])
|
||||
|
||||
const table = useReactTable({
|
||||
data: state.entries,
|
||||
columns: authColumns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel()
|
||||
});
|
||||
|
||||
const textAlignment = {
|
||||
center: 'text-center',
|
||||
left: 'text-start',
|
||||
right: 'text-end'
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!state.isFormOpen) {
|
||||
if (!logbookContext.state.isDrawerOpen) {
|
||||
getLogbookEntries();
|
||||
}
|
||||
}, [state.isFormOpen, state.isTracksOpen]);
|
||||
}, [logbookContext.state.isDrawerOpen]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -215,12 +510,13 @@ const Logbook: React.FC<unknown> = () => {
|
||||
<h1>Logbook</h1>
|
||||
</div>
|
||||
<div className='col-span-2 justify-self-end self-center'>
|
||||
{isUserLoggedIn &&
|
||||
{userRole === UserRole.WRITE &&
|
||||
<Button
|
||||
color='primary'
|
||||
onClick={() => onOpenCloseLogForm(FormMode.ADD)}
|
||||
startContent={<Icon iconName={IconName.PLUS} />}
|
||||
onPress={() => onOpenCloseDrawer(FormMode.ADD)}
|
||||
startContent={<FontAwesomeIcon icon={faAdd} />}
|
||||
data-testid="pilot-add-button"
|
||||
data-theme="lofi"
|
||||
>
|
||||
Add Entry
|
||||
</Button>
|
||||
@@ -232,23 +528,103 @@ const Logbook: React.FC<unknown> = () => {
|
||||
onClose={() =>
|
||||
dispatch({ type: 'SET_ALERT', payload: undefined })
|
||||
}
|
||||
severity={state.alert.severity}
|
||||
>
|
||||
{state.alert.message}
|
||||
</Alert>
|
||||
color={'default'}
|
||||
title={state.alert.message}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!state.isLoading && (
|
||||
<div className='col-span-12'>
|
||||
{state.entries.length > 0 && screenSize !== ScreenSize.SM && (
|
||||
<div className='p-4 z-0 flex flex-col relative justify-between gap-4 bg-content1 overflow-auto shadow-small rounded-large w-full'>
|
||||
<table className='min-w-full h-auto table-auto w-full'>
|
||||
<thead className='[&>tr]:first:rounded-lg'>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr className='group/tr outline-solid outline-transparent data-[focus-visible=true]:z-10 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-focus data-[focus-visible=true]:outline-offset-2' key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<th
|
||||
className={`${header.column.columnDef.meta?.align ? textAlignment[header.column.columnDef.meta?.align] : ''} group/th px-3 h-10 align-middle bg-default-100 whitespace-nowrap text-foreground-500 text-tiny font-semibold first:rounded-s-lg last:rounded-e-lg data-[sortable=true]:cursor-pointer data-[hover=true]:text-foreground-400 outline-solid outline-transparent data-[focus-visible=true]:z-10 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-focus data-[focus-visible=true]:outline-offset-2 text-start`}
|
||||
colSpan={header.colSpan}
|
||||
key={header.id}
|
||||
>
|
||||
{header.isPlaceholder ? null : (
|
||||
<div>
|
||||
{state.columns && state.columns.length > 0 && state.entries.length > 0 && (
|
||||
<Table columns={state.columns} data={state.entries} />
|
||||
{flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
{state.entries.length > 0 &&
|
||||
<LogbookCard logs={state.entries} onDelete={onDeleteLog} mode='logbook' onOpenCloseForm={onOpenCloseLogForm} />
|
||||
{/* {header.column.getCanFilter() ? (
|
||||
<div>
|
||||
<Filter column={header.column} table={table} />
|
||||
</div>
|
||||
) : null} */}
|
||||
</div>
|
||||
)}
|
||||
</th>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody>
|
||||
<>
|
||||
{table.getRowModel().rows.map((row) => {
|
||||
return (
|
||||
<tr className='group/tr outline-solid outline-transparent data-[focus-visible=true]:z-10 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-focus data-[focus-visible=true]:outline-offset-2' key={row.id}>
|
||||
{row.getVisibleCells().map((cell) => {
|
||||
return (
|
||||
<td
|
||||
className={`${cell.column.columnDef.meta?.align ? textAlignment[cell.column.columnDef.meta?.align] : ''} py-2 px-3 relative align-middle whitespace-normal text-small font-normal [&>*]:z-1 [&>*]:relative outline-solid outline-transparent data-[focus-visible=true]:z-10 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-focus data-[focus-visible=true]:outline-offset-2 before:pointer-events-none before:content-[''] before:absolute before:z-0 before:inset-0 before:opacity-0 data-[selected=true]:before:opacity-100 group-data-[disabled=true]/tr:text-foreground-300 group-data-[disabled=true]/tr:cursor-not-allowed before:bg-default/60 data-[selected=true]:text-default-foreground first:before:rounded-s-lg last:before:rounded-e-lg text-start`}
|
||||
key={cell.id}
|
||||
>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext()
|
||||
)}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
</tbody>
|
||||
<thead className='[&>tr]:first:rounded-lg'>
|
||||
{table.getFooterGroups().map((footerGroup, index) => {
|
||||
if (index === 0) {
|
||||
return (
|
||||
<tr className='group/tr outline-solid outline-transparent data-[focus-visible=true]:z-10 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-focus data-[focus-visible=true]:outline-offset-2' key={footerGroup.id}>
|
||||
{footerGroup.headers.map((header) => {
|
||||
return (
|
||||
<td
|
||||
className='group/th px-3 h-10 align-middle bg-default-100 whitespace-nowrap text-foreground-500 text-tiny font-semibold first:rounded-s-lg last:rounded-e-lg data-[sortable=true]:cursor-pointer data-[hover=true]:text-foreground-400 outline-solid outline-transparent data-[focus-visible=true]:z-10 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-focus data-[focus-visible=true]:outline-offset-2 text-start'
|
||||
key={header.id}
|
||||
align={header.column.columnDef.meta?.align}
|
||||
>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.footer,
|
||||
header.getContext()
|
||||
)}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
{state.entries.length > 0 && screenSize === ScreenSize.SM &&
|
||||
<LogbookCard logs={state.entries} onDelete={onDeleteLog} mode='logbook' onOpenCloseForm={onOpenCloseDrawer} />
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
{state.isLoading && !state.alert && (
|
||||
{/* {state.isLoading && !state.alert && (
|
||||
<>
|
||||
<div>
|
||||
<Loading size='xl' />
|
||||
@@ -257,14 +633,11 @@ const Logbook: React.FC<unknown> = () => {
|
||||
Loading...
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
{state.isFormOpen && (
|
||||
<LogForm
|
||||
logId={state.selectedLogId}
|
||||
isDrawerOpen={state.isFormOpen}
|
||||
mode={state.formMode}
|
||||
onOpenClose={(mode) => onOpenCloseLogForm(mode)}
|
||||
{logbookContext.state.isDrawerOpen && (
|
||||
<LogbookDrawer
|
||||
onOpenClose={(mode) => onOpenCloseDrawer(mode)}
|
||||
/>
|
||||
)}
|
||||
{state.isConfirmDialogOpen && (
|
||||
@@ -277,14 +650,14 @@ const Logbook: React.FC<unknown> = () => {
|
||||
title="Confirm Delete"
|
||||
/>
|
||||
)}
|
||||
{state.isTracksOpen &&
|
||||
{/* {state.isTracksOpen &&
|
||||
<LogTracks
|
||||
isDrawerOpen={state.isTracksOpen}
|
||||
mode={state.tracksMode}
|
||||
onOpenClose={(mode) => onOpenCloseTracks(mode)}
|
||||
selectedLogId={state.selectedLogId}
|
||||
selectedLogId={logbookContext.state.selectedLogId}
|
||||
/>
|
||||
}
|
||||
} */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export interface ILogbookEntry {
|
||||
import { Pilot } from "../pilots/Pilot.interface";
|
||||
|
||||
export interface LogbookEntry {
|
||||
id: string;
|
||||
pilot: string;
|
||||
pilot: Pilot;
|
||||
date: string;
|
||||
aircraftMakeModel: string;
|
||||
aircraftIdentity: string;
|
||||
@@ -22,6 +24,6 @@ export interface ILogbookEntry {
|
||||
night: number | null;
|
||||
solo: number | null;
|
||||
pilotInCommand: number | null;
|
||||
tracks: {id: string; order: number; url: string}[];
|
||||
tracks: [];
|
||||
notes: string;
|
||||
}
|
||||
@@ -1,18 +1,13 @@
|
||||
import { ColumnDef } from '@noahspan/noahspan-components';
|
||||
import { FormMode } from '../../enums/formMode';
|
||||
import { Alert } from '../../interfaces/Alert.interface';
|
||||
import { ILogbookEntry } from './ILogbookEntry';
|
||||
import { LogbookEntry } from './LogbookEntry.interface';
|
||||
|
||||
export interface ILogbookState {
|
||||
export interface LogbookState {
|
||||
alert: Alert | undefined;
|
||||
columns: ColumnDef<ILogbookEntry>[];
|
||||
entries: ILogbookEntry[];
|
||||
formMode: FormMode;
|
||||
columns: ColumnDef<LogbookEntry>[];
|
||||
entries: LogbookEntry[];
|
||||
isConfirmDialogLoading: boolean;
|
||||
isConfirmDialogOpen: boolean;
|
||||
isFormOpen: boolean;
|
||||
isLoading: boolean;
|
||||
isTracksOpen: boolean;
|
||||
selectedLogId: string | undefined;
|
||||
tracksMode: FormMode;
|
||||
}
|
||||
@@ -3,9 +3,9 @@ import {
|
||||
ColumnDef,
|
||||
HeaderContext,
|
||||
} from '@noahspan/noahspan-components';
|
||||
import { ILogbookEntry } from './ILogbookEntry';
|
||||
import { LogbookEntry } from './LogbookEntry.interface';
|
||||
|
||||
const columnTotal = (info: HeaderContext<ILogbookEntry, unknown>): number => {
|
||||
const columnTotal = (info: HeaderContext<LogbookEntry, unknown>): number => {
|
||||
const blah = info.table
|
||||
const values: number[] = info.table.getPaginationRowModel().rows.map((row: any) => Number(row.getValue(info.column.id))).filter((value: any) => !Number.isNaN(value));
|
||||
let total: number = 0;
|
||||
@@ -17,34 +17,35 @@ const columnTotal = (info: HeaderContext<ILogbookEntry, unknown>): number => {
|
||||
return total;
|
||||
}
|
||||
|
||||
const pilotName: ColumnDef<ILogbookEntry> = {
|
||||
const pilotName: ColumnDef<LogbookEntry> = {
|
||||
id: 'pilotName',
|
||||
accessorKey: 'pilot',
|
||||
header: 'Pilot',
|
||||
footer: 'PAGE TOTALS',
|
||||
cell: (info: CellContext<ILogbookEntry, unknown>) => {
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) => {
|
||||
const pilot: any = info.getValue();
|
||||
|
||||
return pilot.name
|
||||
}
|
||||
}
|
||||
const date: ColumnDef<ILogbookEntry> = {
|
||||
const date: ColumnDef<LogbookEntry> = {
|
||||
id: 'date',
|
||||
accessorKey: 'date',
|
||||
header: 'Date',
|
||||
cell: (info: CellContext<ILogbookEntry, unknown>) => {
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) => {
|
||||
const date = new Date(info.getValue() as string);
|
||||
const formattedDate = `${date.getMonth()}/${date.getDate()}/${date.getFullYear()}`
|
||||
console.log(date)
|
||||
const formattedDate = `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`
|
||||
|
||||
return formattedDate;
|
||||
}
|
||||
}
|
||||
const aircraftMakeModel: ColumnDef<ILogbookEntry> = {
|
||||
const aircraftMakeModel: ColumnDef<LogbookEntry> = {
|
||||
id: 'aircraftMakeModel',
|
||||
accessorKey: 'aircraftMakeModel',
|
||||
header: 'Aircraft Make & Model'
|
||||
}
|
||||
const route: ColumnDef<ILogbookEntry> = {
|
||||
const route: ColumnDef<LogbookEntry> = {
|
||||
id: 'route',
|
||||
header: 'Route of Flight',
|
||||
meta: {
|
||||
@@ -63,7 +64,7 @@ const route: ColumnDef<ILogbookEntry> = {
|
||||
}
|
||||
]
|
||||
}
|
||||
const durationOfFlight: ColumnDef<ILogbookEntry> = {
|
||||
const durationOfFlight: ColumnDef<LogbookEntry> = {
|
||||
id: 'durationOfFlight',
|
||||
accessorKey: 'durationOfFlight',
|
||||
header: 'Duration Of Flight',
|
||||
@@ -71,17 +72,17 @@ const durationOfFlight: ColumnDef<ILogbookEntry> = {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
},
|
||||
cell: (info: CellContext<ILogbookEntry, unknown>) =>
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) =>
|
||||
info.getValue() ? parseFloat(info.getValue() as string).toFixed(1) : '',
|
||||
footer: (info: HeaderContext<ILogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : ''
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : ''
|
||||
}
|
||||
const notes: ColumnDef<ILogbookEntry> = {
|
||||
const notes: ColumnDef<LogbookEntry> = {
|
||||
id: 'notes',
|
||||
accessorKey: 'notes',
|
||||
header: 'Notes'
|
||||
}
|
||||
|
||||
export const unauthColumns: ColumnDef<ILogbookEntry>[] = [
|
||||
export const unauthColumns: ColumnDef<LogbookEntry>[] = [
|
||||
pilotName,
|
||||
date,
|
||||
aircraftMakeModel,
|
||||
@@ -90,7 +91,7 @@ export const unauthColumns: ColumnDef<ILogbookEntry>[] = [
|
||||
notes
|
||||
]
|
||||
|
||||
export const authColumns: ColumnDef<ILogbookEntry>[] = [
|
||||
export const authColumns: ColumnDef<LogbookEntry>[] = [
|
||||
pilotName,
|
||||
date,
|
||||
aircraftMakeModel,
|
||||
@@ -109,9 +110,9 @@ export const authColumns: ColumnDef<ILogbookEntry>[] = [
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
},
|
||||
cell: (info: CellContext<ILogbookEntry, unknown>) =>
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) =>
|
||||
info.getValue() ? parseFloat(info.getValue() as string).toFixed(1) : '',
|
||||
footer: (info: HeaderContext<ILogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : ''
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : ''
|
||||
},
|
||||
{
|
||||
id: 'landings',
|
||||
@@ -124,7 +125,7 @@ export const authColumns: ColumnDef<ILogbookEntry>[] = [
|
||||
id: 'landingsDay',
|
||||
accessorKey: 'landingsDay',
|
||||
header: 'Day',
|
||||
footer: (info: HeaderContext<ILogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info) : '',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
@@ -134,7 +135,7 @@ export const authColumns: ColumnDef<ILogbookEntry>[] = [
|
||||
id: 'landingsNight',
|
||||
accessorKey: 'landingsNight',
|
||||
header: 'Night',
|
||||
footer: (info: HeaderContext<ILogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info) : '',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
@@ -153,31 +154,31 @@ export const authColumns: ColumnDef<ILogbookEntry>[] = [
|
||||
id: 'instrumentActual',
|
||||
accessorKey: 'instrumentActual',
|
||||
header: 'Actual',
|
||||
footer: (info: HeaderContext<ILogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
},
|
||||
cell: (info: CellContext<ILogbookEntry, unknown>) =>
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) =>
|
||||
info.getValue() ? parseFloat(info.getValue() as string).toFixed(1) : '',
|
||||
},
|
||||
{
|
||||
id: 'instrumentSimulated',
|
||||
accessorKey: 'instrumentSimulated',
|
||||
header: 'Simulated',
|
||||
footer: (info: HeaderContext<ILogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
},
|
||||
cell: (info: CellContext<ILogbookEntry, unknown>) =>
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) =>
|
||||
info.getValue() ? parseFloat(info.getValue() as string).toFixed(1) : ''
|
||||
},
|
||||
{
|
||||
id: 'instrumentApproaches',
|
||||
accessorKey: 'instrumentApproaches',
|
||||
header: 'Approaches',
|
||||
footer: (info: HeaderContext<ILogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
@@ -187,7 +188,7 @@ export const authColumns: ColumnDef<ILogbookEntry>[] = [
|
||||
id: 'instrumentHolds',
|
||||
accessorKey: 'instrumentHolds',
|
||||
header: 'Holds',
|
||||
footer: (info: HeaderContext<ILogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
@@ -197,7 +198,7 @@ export const authColumns: ColumnDef<ILogbookEntry>[] = [
|
||||
id: 'instrumentNavTrack',
|
||||
accessorKey: 'instrumentNavTrack',
|
||||
header: 'Nav/Track',
|
||||
footer: (info: HeaderContext<ILogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
@@ -216,72 +217,72 @@ export const authColumns: ColumnDef<ILogbookEntry>[] = [
|
||||
id: 'groundTrainingReceived',
|
||||
accessorKey: 'groundTrainingReceived',
|
||||
header: 'Ground Training Received',
|
||||
footer: (info: HeaderContext<ILogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
},
|
||||
cell: (info: CellContext<ILogbookEntry, unknown>) =>
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) =>
|
||||
info.getValue() ? parseFloat(info.getValue() as string).toFixed(1) : ''
|
||||
},
|
||||
{
|
||||
id: 'flightTrainingReceived',
|
||||
accessorKey: 'flightTrainingReceived',
|
||||
header: 'Flight Training Received',
|
||||
footer: (info: HeaderContext<ILogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
},
|
||||
cell: (info: CellContext<ILogbookEntry, unknown>) =>
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) =>
|
||||
info.getValue() ? parseFloat(info.getValue() as string).toFixed(1) : ''
|
||||
},
|
||||
{
|
||||
id: 'crossCountry',
|
||||
accessorKey: 'crossCountry',
|
||||
header: 'Cross Country',
|
||||
footer: (info: HeaderContext<ILogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
},
|
||||
cell: (info: CellContext<ILogbookEntry, unknown>) =>
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) =>
|
||||
info.getValue() ? parseFloat(info.getValue() as string).toFixed(1) : ''
|
||||
},
|
||||
{
|
||||
id: 'night',
|
||||
accessorKey: 'night',
|
||||
header: 'Night',
|
||||
footer: (info: HeaderContext<ILogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
},
|
||||
cell: (info: CellContext<ILogbookEntry, unknown>) =>
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) =>
|
||||
info.getValue() ? parseFloat(info.getValue() as string).toFixed(1) : ''
|
||||
},
|
||||
{
|
||||
id: 'solo',
|
||||
accessorKey: 'solo',
|
||||
header: 'Solo',
|
||||
footer: (info: HeaderContext<ILogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
},
|
||||
cell: (info: CellContext<ILogbookEntry, unknown>) =>
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) =>
|
||||
info.getValue() ? parseFloat(info.getValue() as string).toFixed(1) : ''
|
||||
},
|
||||
{
|
||||
id: 'pilotInCommand',
|
||||
accessorKey: 'pilotInCommand',
|
||||
header: 'Pilot In Command',
|
||||
footer: (info: HeaderContext<ILogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
footer: (info: HeaderContext<LogbookEntry, unknown>) => columnTotal(info) > 0 ? columnTotal(info).toFixed(1) : '',
|
||||
meta: {
|
||||
align: 'right',
|
||||
headerAlign: 'right'
|
||||
},
|
||||
cell: (info: CellContext<ILogbookEntry, unknown>) =>
|
||||
cell: (info: CellContext<LogbookEntry, unknown>) =>
|
||||
info.getValue() ? parseFloat(info.getValue() as string).toFixed(1) : ''
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { ColumnDef } from '@noahspan/noahspan-components';
|
||||
import { FormMode } from '../../enums/formMode';
|
||||
import { Alert } from '../../interfaces/Alert.interface';
|
||||
import { ILogbookEntry } from './ILogbookEntry';
|
||||
import { ILogbookState } from './ILogbookState';
|
||||
import { LogbookEntry } from './LogbookEntry.interface';
|
||||
import { LogbookState } from './LogbookState.interface';
|
||||
|
||||
type Action =
|
||||
| { type: 'SET_COLUMNS'; payload: ColumnDef<ILogbookEntry>[] }
|
||||
| { type: 'SET_COLUMNS'; payload: ColumnDef<LogbookEntry>[] }
|
||||
| {
|
||||
type: 'SET_DELETE';
|
||||
payload: {
|
||||
@@ -13,39 +13,25 @@ type Action =
|
||||
selectedLogId: string | undefined;
|
||||
};
|
||||
}
|
||||
| { type: 'SET_ENTRIES'; payload: ILogbookEntry[] }
|
||||
| { type: 'SET_ENTRIES'; payload: LogbookEntry[] }
|
||||
| { type: 'SET_ALERT'; payload: Alert | undefined }
|
||||
| { type: 'SET_FORM_MODE'; payload: FormMode }
|
||||
| { type: 'SET_IS_CONFIRMATION_DIALOG_LOADING'; payload: boolean }
|
||||
| { type: 'SET_IS_LOADING'; payload: boolean }
|
||||
| {
|
||||
type: 'SET_OPEN_CLOSE_LOG_FORM';
|
||||
payload: {
|
||||
formMode: FormMode;
|
||||
selectedLogId: string | undefined;
|
||||
isFormOpen: boolean;
|
||||
};
|
||||
}
|
||||
| { type: 'SET_OPEN_CLOSE_TRACKS'; payload: { tracksMode: FormMode, selectedRowKey: string | undefined, isTracksOpen: boolean; }};
|
||||
| { type: 'SET_IS_LOADING'; payload: boolean };
|
||||
|
||||
export const initialState: ILogbookState = {
|
||||
|
||||
export const initialState: LogbookState = {
|
||||
alert: undefined,
|
||||
columns: [],
|
||||
entries: [],
|
||||
formMode: FormMode.CANCEL,
|
||||
isConfirmDialogLoading: false,
|
||||
isConfirmDialogOpen: false,
|
||||
isFormOpen: false,
|
||||
isLoading: false,
|
||||
isTracksOpen: false,
|
||||
selectedLogId: undefined,
|
||||
tracksMode: FormMode.CANCEL
|
||||
isLoading: false
|
||||
};
|
||||
|
||||
export const reducer = (
|
||||
state: ILogbookState,
|
||||
state: LogbookState,
|
||||
action: Action
|
||||
): ILogbookState => {
|
||||
): LogbookState => {
|
||||
switch (action.type) {
|
||||
case 'SET_COLUMNS': {
|
||||
return {
|
||||
@@ -57,7 +43,6 @@ export const reducer = (
|
||||
return {
|
||||
...state,
|
||||
isConfirmDialogOpen: action.payload.isConfirmationDialogOpen,
|
||||
selectedLogId: action.payload.selectedLogId
|
||||
};
|
||||
}
|
||||
case 'SET_ENTRIES': {
|
||||
@@ -72,12 +57,6 @@ export const reducer = (
|
||||
alert: action.payload
|
||||
};
|
||||
}
|
||||
case 'SET_FORM_MODE': {
|
||||
return {
|
||||
...state,
|
||||
formMode: action.payload
|
||||
};
|
||||
}
|
||||
case 'SET_IS_CONFIRMATION_DIALOG_LOADING': {
|
||||
return {
|
||||
...state,
|
||||
@@ -90,22 +69,6 @@ export const reducer = (
|
||||
isLoading: action.payload
|
||||
};
|
||||
}
|
||||
case 'SET_OPEN_CLOSE_LOG_FORM': {
|
||||
return {
|
||||
...state,
|
||||
formMode: action.payload.formMode,
|
||||
isFormOpen: action.payload.isFormOpen,
|
||||
selectedLogId: action.payload.selectedLogId
|
||||
};
|
||||
}
|
||||
case 'SET_OPEN_CLOSE_TRACKS': {
|
||||
return {
|
||||
...state,
|
||||
tracksMode: action.payload.tracksMode,
|
||||
isTracksOpen: action.payload.isTracksOpen,
|
||||
selectedLogId: action.payload.selectedRowKey
|
||||
}
|
||||
}
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { FormMode } from "../../enums/formMode";
|
||||
import { ILogbookEntry } from "../logbook/ILogbookEntry";
|
||||
import { LogbookEntry } from "../logbook/LogbookEntry.interface";
|
||||
|
||||
export interface LogbookCardProps {
|
||||
logs: ILogbookEntry[];
|
||||
logs: LogbookEntry[];
|
||||
mode: 'flights' | 'logbook';
|
||||
onDelete?: (entryId: string) => void;
|
||||
onOpenCloseForm?: (formMode: FormMode, id: string) => void;
|
||||
|
||||
154
client/src/components/logbookDrawer/LogbookDrawer.tsx
Normal file
154
client/src/components/logbookDrawer/LogbookDrawer.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import { Alert, Button, Drawer, DrawerBody, DrawerContent, DrawerFooter, DrawerHeader, Tab, Tabs } from "@heroui/react";
|
||||
import { LogbookDrawerProps } from "./LogbookDrawerProps.interface";
|
||||
import LogForm from "../logForm/LogForm";
|
||||
import TracksForm from "../tracksForm/TracksForm";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faClock, faMapLocationDot, faSave, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { FormMode } from "../../enums/formMode";
|
||||
import httpClient from "../../httpClient/httpClient";
|
||||
import { AxiosError } from "axios";
|
||||
import { useLogbookContext } from "../../hooks/logbookContext/UseLogbookContext";
|
||||
|
||||
const LogbookDrawer = ({ onOpenClose }: LogbookDrawerProps) => {
|
||||
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: '',
|
||||
tracks: []
|
||||
};
|
||||
const methods = useForm();
|
||||
const logbookContext = useLogbookContext()
|
||||
|
||||
const onCancel = () => {
|
||||
methods.reset(defaultValues);
|
||||
logbookContext.dispatch({ type: 'SET_IS_FORM_DISABLED', payload: false });
|
||||
onOpenClose(FormMode.CANCEL);
|
||||
};
|
||||
|
||||
const onSubmit = async (data: unknown) => {
|
||||
console.log(data)
|
||||
try {
|
||||
logbookContext.dispatch({ type: 'SET_IS_FORM_LOADING', payload: true });
|
||||
|
||||
if (!logbookContext.state.selectedLogId) {
|
||||
await httpClient.post(`api/logs`, data);
|
||||
} else {
|
||||
await httpClient.put(`api/logs/${logbookContext.state.selectedLogId}`, data);
|
||||
}
|
||||
|
||||
methods.reset(defaultValues);
|
||||
logbookContext.dispatch({ type: 'SET_IS_FORM_DISABLED', payload: false });
|
||||
logbookContext.dispatch({ type: 'SET_FORM_MODE', payload: FormMode.CANCEL });
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError;
|
||||
|
||||
logbookContext.dispatch({ type: 'SET_FORM_ALERT', payload: { severity: 'danger', message: axiosError.message }});
|
||||
} finally {
|
||||
logbookContext.dispatch({ type: 'SET_IS_FORM_LOADING', payload: false });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
isOpen={logbookContext.state.isDrawerOpen}
|
||||
>
|
||||
<DrawerContent>
|
||||
<FormProvider {...methods}>
|
||||
<form className='prose max-w-none' onSubmit={methods.handleSubmit(onSubmit)} style={{ paddingBottom: '50px' }}>
|
||||
<DrawerHeader>
|
||||
{`${logbookContext.state.formMode.toString().toLowerCase().charAt(0).toUpperCase() + logbookContext.state.formMode.toString().slice(1).toLowerCase()} Entry`}
|
||||
</DrawerHeader>
|
||||
<DrawerBody>
|
||||
{logbookContext.state.formAlert && (
|
||||
<div className='col-span-12'>
|
||||
<Alert
|
||||
onClose={() =>
|
||||
logbookContext.dispatch({ type: 'SET_FORM_ALERT', payload: undefined })
|
||||
}
|
||||
color={logbookContext.state.formAlert.severity}
|
||||
title={logbookContext.state.formAlert.message}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Tabs color='default' fullWidth={true} variant='solid'>
|
||||
<Tab
|
||||
key='time'
|
||||
title={
|
||||
<div className="flex items-center space-x-2">
|
||||
<FontAwesomeIcon icon={faClock} />
|
||||
<span>Time</span>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<LogForm />
|
||||
</Tab>
|
||||
<Tab
|
||||
key='tracks'
|
||||
title={
|
||||
<div className="flex items-center space-x-2">
|
||||
<FontAwesomeIcon icon={faMapLocationDot} />
|
||||
<span>Tracks</span>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<TracksForm />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</DrawerBody>
|
||||
<DrawerFooter>
|
||||
<div className='grid grid-cols-12 gap-3'>
|
||||
<div className='col-span-12 justify-self-end self-center'>
|
||||
<Button
|
||||
disabled={
|
||||
logbookContext.state.isFormDisabled && logbookContext.state.formMode.toString() !== FormMode.VIEW
|
||||
? logbookContext.state.isFormDisabled
|
||||
: false
|
||||
}
|
||||
startContent={<FontAwesomeIcon icon={faXmark} />}
|
||||
onPress={onCancel}
|
||||
>
|
||||
{logbookContext.state.formMode.toString() !== FormMode.VIEW ? 'Cancel' : 'Close'}
|
||||
</Button>
|
||||
{logbookContext.state.formMode.toString() !== FormMode.VIEW && (
|
||||
<Button
|
||||
className='ml-[10px]'
|
||||
color='primary'
|
||||
disabled={logbookContext.state.isFormDisabled}
|
||||
startContent={<FontAwesomeIcon icon={faSave} />}
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DrawerFooter>
|
||||
</form>
|
||||
</FormProvider>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
export default LogbookDrawer
|
||||
@@ -0,0 +1,5 @@
|
||||
import { FormMode } from "../../enums/formMode";
|
||||
|
||||
export interface LogbookDrawerProps {
|
||||
onOpenClose: (mode: FormMode) => void;
|
||||
}
|
||||
@@ -1,18 +1,17 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm, Controller, FormProvider } from 'react-hook-form';
|
||||
import {
|
||||
Button,
|
||||
Drawer,
|
||||
// Button,
|
||||
// Drawer,
|
||||
Icon,
|
||||
IconButton,
|
||||
IconName,
|
||||
Input,
|
||||
// Input,
|
||||
PeoplePicker,
|
||||
StateSelect
|
||||
} from '@noahspan/noahspan-components';
|
||||
import { IPilotFormProps } from './IPilotFormProps';
|
||||
import { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
|
||||
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { FormMode } from '../../enums/formMode';
|
||||
import { Person } from '@microsoft/microsoft-graph-types';
|
||||
import PilotFormCertificates from '../pilotFormCertificates/PilotFormCertificates';
|
||||
@@ -20,7 +19,10 @@ import PilotFormEndorsements from '../pilotFormEndorsements/PilotFormEndorsement
|
||||
import PilotFormMedical from '../pilotFormMedical/PilotFormMedical';
|
||||
import { useOidc } from '../../auth/oidcConfig';
|
||||
import { getOidc } from '../../auth/oidcConfig';
|
||||
// import httpClient from '../../httpClient/httpClient';
|
||||
import httpClient from '../../httpClient/httpClient';
|
||||
import { Button, Drawer, DrawerHeader, DrawerContent, DrawerBody, DrawerFooter, Input } from '@heroui/react'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
const PilotForm: React.FC<IPilotFormProps> = ({
|
||||
pilotId,
|
||||
@@ -51,7 +53,6 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
||||
});
|
||||
const [isDisabled, setIsDisabled] = useState<boolean>(false);
|
||||
const [isError, setIsError] = useState<boolean>(false);
|
||||
const httpClient = useHttpClient();
|
||||
|
||||
const onPeoplePickerSearch = async (
|
||||
value: string
|
||||
@@ -151,22 +152,25 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
open={isDrawerOpen}
|
||||
position='right'
|
||||
closeButton={
|
||||
<Button isIconOnly>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</Button>
|
||||
}
|
||||
isOpen={isDrawerOpen}
|
||||
placement='right'
|
||||
data-testid="pilot-drawer"
|
||||
width='50%'
|
||||
onClose={onCancel}
|
||||
size='xl'
|
||||
>
|
||||
<DrawerContent>
|
||||
<FormProvider {...methods}>
|
||||
<form className='prose max-w-none' onSubmit={methods.handleSubmit(onSubmit)} style={{ paddingBottom: '50px' }}>
|
||||
<DrawerHeader>
|
||||
{`${mode.toString().toLowerCase().charAt(0).toUpperCase() + mode.toString().slice(1).toLowerCase()} Pilot`}
|
||||
</DrawerHeader>
|
||||
<DrawerBody>
|
||||
<div className='grid grid-cols-12 gap-3'>
|
||||
<div className='col-span-10 self-center'>
|
||||
<h2 style={{ margin: 0 }}>{`${mode.toString().toLowerCase().charAt(0).toUpperCase() + mode.toString().slice(1).toLowerCase()} Pilot`}</h2>
|
||||
</div>
|
||||
<div className='col-span-2 justify-self-end self-center'>
|
||||
<IconButton onClick={onCancel}>
|
||||
<Icon iconName={IconName.XMARK} />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className='col-span-3 self-center'>
|
||||
<h6>Name *</h6>
|
||||
</div>
|
||||
@@ -184,7 +188,7 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
||||
{isUserLoggedIn &&
|
||||
<>
|
||||
<div className='col-span-3 self-center'>
|
||||
<h6>Address *</h6>
|
||||
<span>Address *</span>
|
||||
</div>
|
||||
<div className='col-span-9'>
|
||||
<Controller
|
||||
@@ -194,15 +198,15 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Input
|
||||
disabled={isDisabled}
|
||||
color={methods.formState.errors.address ? 'error' : undefined}
|
||||
helperText={
|
||||
color={methods.formState.errors.address ? 'danger' : undefined}
|
||||
errorMessage={
|
||||
methods.formState.errors.address
|
||||
? methods.formState.errors.address.message
|
||||
: undefined
|
||||
}
|
||||
fullWidth={true}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
width='w-full'
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -218,15 +222,15 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Input
|
||||
disabled={isDisabled}
|
||||
color={methods.formState.errors.city ? 'error' : undefined}
|
||||
helperText={
|
||||
color={methods.formState.errors.city ? 'danger' : undefined}
|
||||
errorMessage={
|
||||
methods.formState.errors.city
|
||||
? methods.formState.errors.city.message
|
||||
: undefined
|
||||
}
|
||||
fullWidth={true}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
width='w-full'
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -267,15 +271,15 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Input
|
||||
disabled={isDisabled}
|
||||
color={methods.formState.errors.postalCode ? 'error' : undefined}
|
||||
helperText={
|
||||
color={methods.formState.errors.postalCode ? 'danger' : undefined}
|
||||
errorMessage={
|
||||
methods.formState.errors.postalCode
|
||||
? methods.formState.errors.postalCode.message
|
||||
: undefined
|
||||
}
|
||||
fullWidth={true}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
width='w-full'
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -296,15 +300,15 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Input
|
||||
disabled={isDisabled}
|
||||
color={methods.formState.errors.email ? 'error' : undefined}
|
||||
helperText={
|
||||
color={methods.formState.errors.email ? 'danger' : undefined}
|
||||
errorMessage={
|
||||
methods.formState.errors.email
|
||||
? methods.formState.errors.email.message
|
||||
: undefined
|
||||
}
|
||||
fullWidth={true}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
width='w-full'
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -325,15 +329,15 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Input
|
||||
disabled={isDisabled}
|
||||
color={methods.formState.errors.phone ? 'error' : undefined}
|
||||
helperText={
|
||||
color={methods.formState.errors.phone ? 'danger' : undefined}
|
||||
errorMessage={
|
||||
methods.formState.errors.phone
|
||||
? methods.formState.errors.phone.message
|
||||
: undefined
|
||||
}
|
||||
fullWidth={true}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
width='w-full'
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -353,7 +357,12 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
||||
<Grid size={12}>
|
||||
<PilotFormEndorsements isDisabled={isDisabled} mode={mode} />
|
||||
</Grid> */}
|
||||
<div className='col-span-12 justify-self-end self-center'>
|
||||
{/* <div className='col-span-12 justify-self-end self-center'> */}
|
||||
|
||||
{/* </div> */}
|
||||
</div>
|
||||
</DrawerBody>
|
||||
<DrawerFooter>
|
||||
<Button
|
||||
disabled={
|
||||
isDisabled && mode.toString() !== FormMode.VIEW
|
||||
@@ -361,8 +370,7 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
||||
: false
|
||||
}
|
||||
startContent={<Icon iconName={IconName.XMARK} />}
|
||||
onClick={onCancel}
|
||||
outline={true}
|
||||
onPress={onCancel}
|
||||
data-testid="pilot-cancel-button"
|
||||
>
|
||||
{mode.toString() !== FormMode.VIEW ? 'Cancel' : 'Close'}
|
||||
@@ -378,10 +386,10 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
||||
Save
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DrawerFooter>
|
||||
</form>
|
||||
</FormProvider>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
export interface Pilot {
|
||||
id: string;
|
||||
name: string;
|
||||
address: string;
|
||||
city: string;
|
||||
state: string;
|
||||
postalCode: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
};
|
||||
@@ -1,14 +1,15 @@
|
||||
import { useEffect, useReducer, useState } from 'react';
|
||||
import PilotForm from '../pilotForm/PilotForm';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
ColumnDef,
|
||||
// Alert,
|
||||
// Button,
|
||||
// ColumnDef,
|
||||
// Dropdown,
|
||||
Icon,
|
||||
IconButton,
|
||||
IconName,
|
||||
Table,
|
||||
// Table,
|
||||
} from '@noahspan/noahspan-components';
|
||||
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
||||
import { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
|
||||
import { FormMode } from '../../enums/formMode';
|
||||
import { Pilot } from './Pilot.interface';
|
||||
@@ -19,18 +20,30 @@ import PilotCard from '../pilotCard/PilotCard';
|
||||
import { getOidc, useOidc } from '../../auth/oidcConfig';
|
||||
import { useUserRole } from '../../hooks/userRole/UseUserRole';
|
||||
import { UserRole } from '../../enums/userRole';
|
||||
import httpClient from '../../httpClient/httpClient'
|
||||
import { ScreenSize } from '../../enums/screenSize';
|
||||
import { useBreakpoints } from '../../hooks/useBreakpoints/UseBreakpoints';
|
||||
import { Alert, Button, Dropdown, DropdownItem, DropdownSection, Table, TableHeader, TableBody, TableColumn, TableRow, TableCell, DropdownTrigger, DropdownMenu } from '@heroui/react'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faEllipsisVertical, faPen, faEye, faTrash } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
const Pilots: React.FC<unknown> = () => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const httpClient: AxiosInstance = useHttpClient();
|
||||
const { isUserLoggedIn, decodedIdToken } = useOidc();
|
||||
const userRole = useUserRole();
|
||||
// const { httpClient } = useHttpClient();
|
||||
const { userRole } = useUserRole();
|
||||
const { screenSize } = useBreakpoints()
|
||||
|
||||
const getPilots = async () => {
|
||||
try {
|
||||
const response: AxiosResponse = await httpClient.get(
|
||||
// const oidc = await getOidc();
|
||||
let response: AxiosResponse;
|
||||
// if (oidc.isUserLoggedIn) {
|
||||
// const { accessToken } = await oidc.getTokens();
|
||||
console.log('blah')
|
||||
response = await httpClient.get(
|
||||
`api/pilots`
|
||||
);
|
||||
|
||||
console.log(response);
|
||||
if (response.data.length > 0) {
|
||||
dispatch({ type: 'SET_PILOTS', payload: response.data });
|
||||
@@ -39,14 +52,15 @@ const Pilots: React.FC<unknown> = () => {
|
||||
dispatch({ type: 'SET_ALERT', payload: undefined })
|
||||
}
|
||||
} else {
|
||||
dispatch({ type: 'SET_ALERT', payload: { severity: 'info', message: 'No pilots found.' }})
|
||||
dispatch({ type: 'SET_ALERT', payload: { severity: 'default', message: 'No pilots found.' }})
|
||||
}
|
||||
// }
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError;
|
||||
|
||||
dispatch({
|
||||
type: 'SET_ALERT',
|
||||
payload: { severity: 'error', message: `Loading of pilots failed with the following message: ${axiosError.message}`}
|
||||
payload: { severity: 'danger', message: `Loading of pilots failed with the following message: ${axiosError.message}`}
|
||||
})
|
||||
} finally {
|
||||
dispatch({ type: 'SET_IS_LOADING', payload: false })
|
||||
@@ -105,7 +119,7 @@ const Pilots: React.FC<unknown> = () => {
|
||||
|
||||
dispatch({
|
||||
type: 'SET_ALERT',
|
||||
payload: { severity: 'error', message: `Loading of logbook entries failed with the following message: ${axiosError.message}`}
|
||||
payload: { severity: 'danger', message: `Loading of logbook entries failed with the following message: ${axiosError.message}`}
|
||||
});
|
||||
} finally {
|
||||
dispatch({ type: 'SET_IS_CONFIRMATION_DIALOG_LOADING', payload: false });
|
||||
@@ -119,23 +133,94 @@ const Pilots: React.FC<unknown> = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const columns: ColumnDef<Pilot>[] = [
|
||||
const columns = [
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: 'Name'
|
||||
id: 'name',
|
||||
name: 'Name'
|
||||
},
|
||||
{
|
||||
header: 'Actions',
|
||||
cell: (info: any) => {
|
||||
console.log(info)
|
||||
return <ActionMenu
|
||||
id={info.row.original.id}
|
||||
onDelete={onDeleteEntry}
|
||||
onOpenCloseForm={onOpenClosePilotForm}
|
||||
/>
|
||||
id: 'actions',
|
||||
name: 'Actions'
|
||||
}
|
||||
]
|
||||
|
||||
// const columns: ColumnDef<Pilot>[] = [
|
||||
// {
|
||||
// accessorKey: 'name',
|
||||
// header: 'Name'
|
||||
// },
|
||||
// {
|
||||
// header: 'Actions',
|
||||
// cell: (info: any) => {
|
||||
// return (
|
||||
// <Dropdown
|
||||
// options={[
|
||||
// 'Edit',
|
||||
// 'View',
|
||||
// 'Delete'
|
||||
// ]}
|
||||
// onOptionSelected={() => console.log(info.row.original.id)}
|
||||
// >
|
||||
// <IconButton>
|
||||
// <Icon className='text-xl' iconName={IconName.ELLIPSIS_VERTICAL} />
|
||||
// </IconButton>
|
||||
// </Dropdown>
|
||||
// )
|
||||
// // return <ActionMenu
|
||||
// // id={info.row.original.id}
|
||||
// // onDelete={onDeleteEntry}
|
||||
// // onOpenCloseForm={onOpenClosePilotForm}
|
||||
// // />
|
||||
// }
|
||||
// }
|
||||
// ];
|
||||
|
||||
const renderCell = (pilot: any, columnKey: any) => {
|
||||
const cellValue = pilot[columnKey]
|
||||
console.log(cellValue)
|
||||
switch (columnKey) {
|
||||
case 'actions': {
|
||||
return (
|
||||
<Dropdown>
|
||||
<DropdownTrigger>
|
||||
<Button isIconOnly variant='light' size='lg'>
|
||||
<FontAwesomeIcon icon={faEllipsisVertical} />
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu>
|
||||
<DropdownSection showDivider>
|
||||
<DropdownItem
|
||||
key='edit'
|
||||
onPress={() => onOpenClosePilotForm(FormMode.EDIT)}
|
||||
startContent={<FontAwesomeIcon icon={faPen} />}
|
||||
>
|
||||
Edit
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
key='view'
|
||||
onPress={() => onOpenClosePilotForm(FormMode.VIEW)}
|
||||
startContent={<FontAwesomeIcon icon={faEye} />}
|
||||
>
|
||||
View
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
<DropdownSection>
|
||||
<DropdownItem
|
||||
key='Delete'
|
||||
startContent={<FontAwesomeIcon icon={faTrash} />}
|
||||
>
|
||||
Delete
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
default: {
|
||||
return cellValue
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (!state.isFormOpen) {
|
||||
@@ -150,7 +235,7 @@ const Pilots: React.FC<unknown> = () => {
|
||||
<h1>Pilots</h1>
|
||||
</div>
|
||||
<div className='col-span-2 justify-self-end self-center'>
|
||||
{userRole && userRole !== UserRole.READ &&
|
||||
{userRole === UserRole.WRITE &&
|
||||
<Button
|
||||
color='primary'
|
||||
onClick={() => onOpenClosePilotForm(FormMode.ADD)}
|
||||
@@ -167,20 +252,40 @@ const Pilots: React.FC<unknown> = () => {
|
||||
onClose={() =>
|
||||
dispatch({ type: 'SET_ALERT', payload: undefined })
|
||||
}
|
||||
severity={state.alert.severity}
|
||||
>
|
||||
{state.alert.message}
|
||||
</Alert>
|
||||
color={state.alert.severity}
|
||||
title={state.alert.message}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className='col-span-12'>
|
||||
{state.pilots.length > 0 &&
|
||||
<Table columns={columns} data={state.pilots} />
|
||||
{state.pilots.length > 0 && screenSize !== ScreenSize.SM &&
|
||||
// <Table columns={columns} data={state.pilots} />
|
||||
<Table>
|
||||
<TableHeader columns={columns}>
|
||||
{(column) => (
|
||||
<TableColumn
|
||||
key={column.id}
|
||||
align={column.id === "actions" ? "center" : "start"}
|
||||
>
|
||||
{column.name}
|
||||
</TableColumn>
|
||||
)}
|
||||
</TableHeader>
|
||||
<TableBody items={state.pilots}>
|
||||
{(item) => (
|
||||
<TableRow key={item.id}>
|
||||
{(columnKey => (
|
||||
<TableCell>
|
||||
{renderCell(item, columnKey)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
}
|
||||
{state.pilots.length > 0 &&
|
||||
<div className='lg:hidden'>
|
||||
{state.pilots.length > 0 && screenSize === ScreenSize.SM &&
|
||||
<PilotCard pilots={state.pilots} onDelete={onDeleteEntry} onOpenCloseForm={onOpenClosePilotForm} />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,38 +1,36 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useAppContext } from '../../hooks/appContext/UseAppContext';
|
||||
import {
|
||||
Dropdown,
|
||||
Icon,
|
||||
IconName,
|
||||
Navbar,
|
||||
} from '@noahspan/noahspan-components';
|
||||
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
||||
import { AxiosInstance, AxiosResponse } from 'axios';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { User } from '@microsoft/microsoft-graph-types';
|
||||
import { getOidc, useOidc } from '../../auth/oidcConfig';
|
||||
import { Button, Link, Navbar, NavbarBrand, NavbarContent, NavbarItem, NavbarMenuToggle, NavbarMenu, NavbarMenuItem } from '@heroui/react';
|
||||
import httpClient from '../../httpClient/httpClient'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faPlane } from '@fortawesome/free-solid-svg-icons'
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
const SiteNav = () => {
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [userPhoto, setUserPhoto] = useState<string>();
|
||||
const [pages, setPages] = useState<{ name: string; url: string; }[]>([]);
|
||||
const httpClient: AxiosInstance = useHttpClient();
|
||||
const [pages, setPages] = useState<{ name: string; path: string; }[]>([]);
|
||||
const appContext = useAppContext();
|
||||
const { isUserLoggedIn, login, logout } = useOidc()
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = useLocation()
|
||||
const getPages = () => {
|
||||
const pages = [
|
||||
{
|
||||
name: 'Flights',
|
||||
url: '/'
|
||||
path: '/'
|
||||
},
|
||||
{
|
||||
name: 'Logbook',
|
||||
url: '/logbook'
|
||||
path: '/logbook'
|
||||
},
|
||||
{
|
||||
name: 'Pilots',
|
||||
url: '/pilots'
|
||||
path: '/pilots'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -85,16 +83,16 @@ const SiteNav = () => {
|
||||
navigate(url);
|
||||
};
|
||||
|
||||
const Settings = () => {
|
||||
return (
|
||||
<div>
|
||||
<Icon iconName={IconName.SIGN_OUT} />
|
||||
<span>
|
||||
Sign Out
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
// const Settings = () => {
|
||||
// return (
|
||||
// <div>
|
||||
// <Icon iconName={IconName.SIGN_OUT} />
|
||||
// <span>
|
||||
// Sign Out
|
||||
// </span>
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
|
||||
useEffect(() => {
|
||||
const setUserProfile = async () => {
|
||||
@@ -138,17 +136,38 @@ const SiteNav = () => {
|
||||
getPages();
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
console.log(pathname)
|
||||
}, [pathname])
|
||||
|
||||
return (
|
||||
<Navbar
|
||||
authenticated={isUserLoggedIn}
|
||||
color='base'
|
||||
handlePageClick={handlePageClick}
|
||||
handleSignIn={handleSignIn}
|
||||
logo={<Icon className='mt-1' iconName={IconName.PLANE} size="2x" />}
|
||||
pages={pages}
|
||||
settings={<Settings />}
|
||||
userPhoto={userPhoto}
|
||||
<Navbar isBordered maxWidth='full' position='static'>
|
||||
<NavbarBrand>
|
||||
<img
|
||||
height={35}
|
||||
width={35}
|
||||
src='noahspan-logo.png'
|
||||
style={{ marginRight: '5px' }}
|
||||
/>
|
||||
<FontAwesomeIcon icon={faPlane} size='2x' />
|
||||
</NavbarBrand>
|
||||
<NavbarContent justify='center'>
|
||||
{pages.length > 0 && pages.map((page) => {
|
||||
return (
|
||||
<NavbarItem isActive={pathname === page.path ? true : false}>
|
||||
<Link color={pathname === page.path ? 'primary' : 'foreground'} href={page.path}>
|
||||
{page.name}
|
||||
</Link>
|
||||
</NavbarItem>
|
||||
)
|
||||
})}
|
||||
</NavbarContent>
|
||||
<NavbarContent justify='end'>
|
||||
<Button color='default' onClick={handleSignIn} variant='flat'>
|
||||
Sign In
|
||||
</Button>
|
||||
</NavbarContent>
|
||||
</Navbar>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
224
client/src/components/tracksForm/TracksForm.tsx
Normal file
224
client/src/components/tracksForm/TracksForm.tsx
Normal file
@@ -0,0 +1,224 @@
|
||||
import { useEffect, useReducer } from "react";
|
||||
// import { Button, Drawer, Icon, IconButton, IconName, Input, Loading } from "@noahspan/noahspan-components";
|
||||
import { AxiosError, AxiosInstance, 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 httpClient from "../../httpClient/httpClient";
|
||||
import { Button, Drawer, DrawerContent, DrawerBody, DrawerHeader, Input, Spinner, DrawerFooter } 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 handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
console.log('blah')
|
||||
try {
|
||||
dispatch({ type: 'SET_IS_LOADING', payload: true})
|
||||
|
||||
const file = event.target.files![0]
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('file', file);
|
||||
|
||||
httpClient.interceptors.request.use((config) => {
|
||||
config.headers["Content-Type"] = 'multipart/form-data'
|
||||
|
||||
return config
|
||||
})
|
||||
|
||||
const uploadResponse: AxiosResponse = await httpClient.post(`api/tracks/${logbookContext.state.selectedLogId}/1`, formData);
|
||||
// const uploadUrl = uploadResponse.data.url;
|
||||
// const tracks: string[] = log.tracks ? JSON.parse(log.tracks!) : [];
|
||||
|
||||
// tracks.push(uploadUrl)
|
||||
// log.tracks = JSON.stringify(tracks);
|
||||
// await httpClient.put(`api/logs/log/${logbookContext.state.selectedLogId}`, log, config);
|
||||
|
||||
// const updatedLog = await getLog();
|
||||
|
||||
// dispatch({ type: 'SET_TRACKS', payload: JSON.parse(updatedLog.tracks!) })
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
} finally {
|
||||
dispatch({ type: 'SET_IS_LOADING', payload: false });
|
||||
}
|
||||
}
|
||||
|
||||
// const onDeleteTrack = async (index: number) => {
|
||||
// dispatch({ type: 'SET_ON_DELETE_TRACK', payload: { isConfirmDialogOpen: true, selectedTrack: { index: index }}})
|
||||
// }
|
||||
|
||||
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])
|
||||
|
||||
return (
|
||||
<div className='grid grid-cols-12 gap-3'>
|
||||
<>
|
||||
{state.tracks.length > 0 &&
|
||||
<>
|
||||
<div className='col-span-10'>
|
||||
<Input type='text' />
|
||||
</div>
|
||||
<div className='col-span-2'>
|
||||
<Button isIconOnly onPress={() => console.log('delete')}><FontAwesomeIcon icon={faTrash} /></Button>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
<div className='col-span-12'>
|
||||
<Button
|
||||
as='label'
|
||||
disabled={state.isLoading ? true : false}
|
||||
fullWidth={true}
|
||||
startContent={<FontAwesomeIcon icon={faUpload} />}
|
||||
>
|
||||
Upload Track
|
||||
<input hidden onChange={handleFileUpload} type='file' />
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// const getConfig = async () => {
|
||||
// const config = auth.isAuthenticated
|
||||
// ? { headers: { Authorization: auth.user?.access_token } }
|
||||
// : {};
|
||||
|
||||
// return config
|
||||
// }
|
||||
|
||||
// const getLog = async (): Promise<ILogbookEntry> => {
|
||||
// const logResponse: AxiosResponse = await httpClient.get(
|
||||
// `api/tracks/${selectedLogId}`,
|
||||
// await getConfig()
|
||||
// );
|
||||
// const logData: ILogbookEntry = logResponse.data;
|
||||
|
||||
// return logData
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// const onCancel = () => {
|
||||
// onOpenClose(FormMode.CANCEL)
|
||||
// }
|
||||
|
||||
// const onDeleteTrack = async (fileName: string, index: number) => {
|
||||
// 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 () => {
|
||||
// const log = await getLog();
|
||||
|
||||
// dispatch({ type: 'SET_TRACKS', payload: log.tracks! });
|
||||
// }
|
||||
|
||||
// updateTracks();
|
||||
// }, [])
|
||||
|
||||
// return (
|
||||
// <div className='grid grid-cols-12 gap-3'>
|
||||
// {}
|
||||
// {logbookContext.state.formMode === FormMode.EDIT &&
|
||||
// <>
|
||||
// {state.isLoading &&
|
||||
// <>
|
||||
// <div>
|
||||
// <Spinner size='lg' />
|
||||
// </div>
|
||||
// <div>
|
||||
// Loading...
|
||||
// </div>
|
||||
// </>
|
||||
// }
|
||||
// {!state.isLoading && state.tracks.length > 0 && state.tracks.map((track, index) => {
|
||||
// const trackSplit = track.url.split('/')
|
||||
// const filename = trackSplit[trackSplit.length - 1];
|
||||
|
||||
// return (
|
||||
// <>
|
||||
// <div>
|
||||
// <Input disabled={true} value={filename} />
|
||||
// </div>
|
||||
// <div>
|
||||
// <Button isIconOnly onPress={() => onDeleteTrack(filename, index)}><FontAwesomeIcon icon={faTrash} /></Button>
|
||||
// </div>
|
||||
// </>
|
||||
// )
|
||||
// })}
|
||||
// </>
|
||||
// }
|
||||
{/* {logbookContext.state.formMode === FormMode.VIEW &&
|
||||
<LogTrackMaps logId={logbookContext.state.selectedLogId!} tracks={state.tracks} />
|
||||
} */}
|
||||
{/* {state.isConfirmDialogOpen && (
|
||||
<ConfirmationDialog
|
||||
contentText="Are you sure you want to delete this track?"
|
||||
isLoading={state.isConfirmDialogLoading}
|
||||
isOpen={state.isConfirmDialogOpen}
|
||||
onCancel={onConfirmDialogCancel}
|
||||
onConfirm={onConfirmDialogConfirm}
|
||||
title="Confirm Delete"
|
||||
/>
|
||||
)} */}
|
||||
// </div>
|
||||
// )
|
||||
// }
|
||||
|
||||
export default TracksForm;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FormMode } from "../../enums/formMode";
|
||||
|
||||
export interface LogTracksProps {
|
||||
export interface TracksFormProps {
|
||||
isDrawerOpen: boolean;
|
||||
mode: FormMode;
|
||||
onOpenClose: (mode: FormMode) => void;
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface LogTracksState {
|
||||
export interface TracksFormState {
|
||||
isConfirmDialogOpen: boolean;
|
||||
isConfirmDialogLoading: boolean;
|
||||
isLoading: boolean;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LogTracksState } from "./LogTracksState.interface";
|
||||
import { TracksFormState } from "./TracksFormState.interface";
|
||||
|
||||
type Action =
|
||||
| { type: 'SET_IS_CONFIRM_DIALOG_OPEN'; payload: boolean }
|
||||
@@ -7,7 +7,7 @@ type Action =
|
||||
| { type: 'SET_ON_DELETE_TRACK'; payload: { isConfirmDialogOpen: boolean, selectedTrack: { fileName: string, index: number } }}
|
||||
| { type: 'SET_TRACKS'; payload: { id: string; order: number; url: string }[] };
|
||||
|
||||
export const initialState: LogTracksState = {
|
||||
export const initialState: TracksFormState = {
|
||||
isConfirmDialogOpen: false,
|
||||
isConfirmDialogLoading: false,
|
||||
isLoading: false,
|
||||
@@ -15,7 +15,7 @@ export const initialState: LogTracksState = {
|
||||
tracks: []
|
||||
}
|
||||
|
||||
export const reducer = (state: LogTracksState, action: Action): LogTracksState => {
|
||||
export const reducer = (state: TracksFormState, action: Action): TracksFormState => {
|
||||
switch (action.type) {
|
||||
case 'SET_IS_CONFIRM_DIALOG_OPEN': {
|
||||
return {
|
||||
5
client/src/context/logbookContext/LogbookContext.tsx
Normal file
5
client/src/context/logbookContext/LogbookContext.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Context, createContext } from 'react';
|
||||
import { LogbookContextProps } from './LogbookContextProps.interface';
|
||||
|
||||
export const LogbookContext: Context<LogbookContextProps> =
|
||||
createContext<LogbookContextProps>({} as LogbookContextProps);
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Action } from './reducer';
|
||||
import { LogbookContextState } from './LogbookContextState.interface';
|
||||
|
||||
export interface LogbookContextProps {
|
||||
state: LogbookContextState;
|
||||
dispatch: React.Dispatch<Action>;
|
||||
}
|
||||
35
client/src/context/logbookContext/LogbookContextProvider.tsx
Normal file
35
client/src/context/logbookContext/LogbookContextProvider.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { useMemo, useReducer } from 'react';
|
||||
import { LogbookContext } from './LogbookContext';
|
||||
import { LogbookContextProviderProps } from './LogbookContextProviderProps.interface';
|
||||
import { LogbookContextProps } from './LogbookContextProps.interface';
|
||||
import { LogbookContextState } from './LogbookContextState.interface';
|
||||
import { reducer } from './reducer';
|
||||
import { FormMode } from '../../enums/formMode';
|
||||
|
||||
const AppContextProvider: React.FC<LogbookContextProviderProps> = (
|
||||
props: LogbookContextProviderProps
|
||||
) => {
|
||||
const initialState: LogbookContextState = {
|
||||
formAlert: undefined,
|
||||
formMode: FormMode.VIEW,
|
||||
isDrawerOpen: false,
|
||||
isFormDisabled: false,
|
||||
isFormLoading: false,
|
||||
selectedLogId: ''
|
||||
}
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const contextValue: LogbookContextProps = useMemo(() => {
|
||||
return {
|
||||
state,
|
||||
dispatch
|
||||
};
|
||||
}, [state, dispatch]);
|
||||
|
||||
return (
|
||||
<LogbookContext.Provider value={contextValue}>
|
||||
{props.children}
|
||||
</LogbookContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppContextProvider;
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface LogbookContextProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { FormMode } from "../../enums/formMode";
|
||||
import { Alert } from "../../interfaces/Alert.interface";
|
||||
|
||||
export interface LogbookContextState {
|
||||
formAlert: Alert | undefined ;
|
||||
formMode: FormMode;
|
||||
isDrawerOpen: boolean;
|
||||
isFormDisabled: boolean;
|
||||
isFormLoading: boolean;
|
||||
selectedLogId: string | undefined;
|
||||
}
|
||||
67
client/src/context/logbookContext/reducer.ts
Normal file
67
client/src/context/logbookContext/reducer.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { FormMode } from '../../enums/formMode';
|
||||
import { Alert } from '../../interfaces/Alert.interface';
|
||||
import { LogbookContextState } from './LogbookContextState.interface';
|
||||
|
||||
export type Action =
|
||||
| { type: 'SET_FORM_ALERT'; payload: Alert | undefined }
|
||||
| { type: 'SET_FORM_MODE'; payload: FormMode }
|
||||
| { type: 'SET_IS_DRAWER_OPEN'; payload: boolean }
|
||||
| { type: 'SET_IS_FORM_DISABLED'; payload: boolean }
|
||||
| { type: 'SET_IS_FORM_LOADING'; payload: boolean }
|
||||
| { type: 'SET_OPEN_CLOSE_DRAWER'; payload: { formMode: FormMode, isDrawerOpen: boolean, selectedLogId: string | undefined }}
|
||||
| { type: 'SET_SELECTED_LOG_ID'; payload: string }
|
||||
|
||||
export const reducer = (
|
||||
state: LogbookContextState,
|
||||
action: Action
|
||||
): LogbookContextState => {
|
||||
switch (action.type) {
|
||||
case 'SET_FORM_ALERT': {
|
||||
return {
|
||||
...state,
|
||||
formAlert: action.payload
|
||||
};
|
||||
}
|
||||
case 'SET_FORM_MODE': {
|
||||
return {
|
||||
...state,
|
||||
formMode: action.payload
|
||||
}
|
||||
}
|
||||
case 'SET_IS_DRAWER_OPEN': {
|
||||
return {
|
||||
...state,
|
||||
isDrawerOpen: action.payload
|
||||
};
|
||||
}
|
||||
case 'SET_IS_FORM_DISABLED': {
|
||||
return {
|
||||
...state,
|
||||
isFormDisabled: action.payload
|
||||
}
|
||||
}
|
||||
case 'SET_IS_FORM_LOADING': {
|
||||
return {
|
||||
...state,
|
||||
isFormLoading: action.payload
|
||||
}
|
||||
}
|
||||
case 'SET_OPEN_CLOSE_DRAWER': {
|
||||
return {
|
||||
...state,
|
||||
formMode: action.payload.formMode,
|
||||
isDrawerOpen: action.payload.isDrawerOpen,
|
||||
selectedLogId: action.payload.selectedLogId
|
||||
}
|
||||
}
|
||||
case 'SET_SELECTED_LOG_ID': {
|
||||
return {
|
||||
...state,
|
||||
selectedLogId: action.payload
|
||||
}
|
||||
}
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
||||
7
client/src/enums/screenSize.ts
Normal file
7
client/src/enums/screenSize.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export enum ScreenSize {
|
||||
SM,
|
||||
MD,
|
||||
LG,
|
||||
XL,
|
||||
XXL
|
||||
}
|
||||
3
client/src/hero.ts
Normal file
3
client/src/hero.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// hero.ts
|
||||
import { heroui } from "@heroui/react";
|
||||
export default heroui();
|
||||
@@ -1,29 +0,0 @@
|
||||
import axios, { AxiosInstance, CreateAxiosDefaults } from 'axios';
|
||||
|
||||
export const useHttpClient = () => {
|
||||
let config: CreateAxiosDefaults<any> = {
|
||||
baseURL: import.meta.env.VITE_API_URL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
config = {
|
||||
baseURL: import.meta.env.VITE_API_URL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
} else {
|
||||
config = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const httpClient: AxiosInstance = axios.create(config);
|
||||
|
||||
return httpClient;
|
||||
};
|
||||
11
client/src/hooks/logbookContext/UseLogbookContext.tsx
Normal file
11
client/src/hooks/logbookContext/UseLogbookContext.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { useContext } from 'react';
|
||||
import { LogbookContext } from '../../context/logbookContext/LogbookContext';
|
||||
|
||||
export const useLogbookContext = () => {
|
||||
const { state, dispatch } = useContext(LogbookContext);
|
||||
|
||||
return {
|
||||
state,
|
||||
dispatch
|
||||
};
|
||||
};
|
||||
@@ -1,37 +1,30 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useHttpClient } from "../httpClient/UseHttpClient";
|
||||
import { useAuth } from 'react-oidc-context';
|
||||
import { AxiosInstance, AxiosResponse } from "axios";
|
||||
import { ILogbookEntry } from "../../components/logbook/ILogbookEntry";
|
||||
import { LogbookEntry } from "../../components/logbook/LogbookEntry.interface";
|
||||
import httpClient from '../../httpClient/httpClient'
|
||||
|
||||
export const useLogs = () => {
|
||||
const [logs, setLogs] = useState<ILogbookEntry[]>();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const httpClient: AxiosInstance = useHttpClient();
|
||||
const [logs, setLogs] = useState<LogbookEntry[]>();
|
||||
const [logsLoading, setLogsLoading] = useState<boolean>(false);
|
||||
const auth = useAuth()
|
||||
|
||||
useEffect(() => {
|
||||
const getLogs = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setLogsLoading(true);
|
||||
|
||||
const response: AxiosResponse = await httpClient.get(
|
||||
`api/logs`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: auth.user?.access_token
|
||||
}
|
||||
}
|
||||
`api/logs`
|
||||
);
|
||||
const logs: ILogbookEntry[] = response.data;
|
||||
const logs: LogbookEntry[] = response.data;
|
||||
|
||||
logs.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
|
||||
|
||||
setLogs(logs)
|
||||
} catch (error) {
|
||||
return error;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setLogsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +33,6 @@ export const useLogs = () => {
|
||||
|
||||
return {
|
||||
logs,
|
||||
isLoading
|
||||
logsLoading
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,14 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { AxiosInstance, AxiosResponse } from 'axios';
|
||||
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
||||
import { useAuth } from 'react-oidc-context';
|
||||
import httpClient from '../../httpClient/httpClient';
|
||||
|
||||
export const usePilots = () => {
|
||||
const [pilots, setPilots] = useState<any[]>();
|
||||
const httpClient: AxiosInstance = useHttpClient();
|
||||
const auth = useAuth();
|
||||
|
||||
const getPilot = async (pilotId: string) => {
|
||||
try {
|
||||
const response: AxiosResponse = await httpClient.get(
|
||||
`api/pilots/${pilotId}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: auth.user?.access_token
|
||||
}
|
||||
}
|
||||
`api/pilots/${pilotId}`
|
||||
);
|
||||
|
||||
return response.data;
|
||||
@@ -29,14 +21,9 @@ export const usePilots = () => {
|
||||
const getPilots = async () => {
|
||||
try {
|
||||
const response: AxiosResponse = await httpClient.get(
|
||||
`api/pilots`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: auth.user?.access_token
|
||||
}
|
||||
}
|
||||
`/api/pilots`
|
||||
);
|
||||
|
||||
console.log(response)
|
||||
setPilots(response.data);
|
||||
} catch (error) {
|
||||
return error;
|
||||
|
||||
66
client/src/hooks/useBreakpoints/UseBreakpoints.tsx
Normal file
66
client/src/hooks/useBreakpoints/UseBreakpoints.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ScreenSize } from '../../enums/screenSize';
|
||||
|
||||
export const useBreakpoints = () => {
|
||||
const [screenSize, setScreenSize] = useState<ScreenSize>();
|
||||
const windowWidth = window.innerWidth;
|
||||
|
||||
const getWindowSize = (width: number): ScreenSize => {
|
||||
let size!: ScreenSize;
|
||||
|
||||
switch (true) {
|
||||
case width < 640: {
|
||||
size = ScreenSize.SM;
|
||||
console.log('small')
|
||||
break;
|
||||
}
|
||||
case width >= 640: {
|
||||
size = ScreenSize.MD;
|
||||
|
||||
break;
|
||||
}
|
||||
case width >= 1024: {
|
||||
size = ScreenSize.LG
|
||||
|
||||
break;
|
||||
}
|
||||
case width >= 1280: {
|
||||
size = ScreenSize.XL;
|
||||
|
||||
break;
|
||||
}
|
||||
case width >= 1536: {
|
||||
size = ScreenSize.XXL;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
const onWindowResize = () => {
|
||||
const width: number = window.innerWidth;
|
||||
const newScreenSize: ScreenSize = getWindowSize(width);
|
||||
console.log(newScreenSize)
|
||||
setScreenSize(newScreenSize);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log(windowWidth)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
onWindowResize()
|
||||
|
||||
window.addEventListener('resize', onWindowResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', onWindowResize);
|
||||
}
|
||||
}, [])
|
||||
|
||||
return {
|
||||
screenSize
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
|
||||
import { useOidc } from "../../auth/oidcConfig";
|
||||
import { UserRole } from "../../enums/userRole";
|
||||
|
||||
export const useUserRole = (): UserRole | undefined => {
|
||||
export const useUserRole = () => {
|
||||
const [userRole, setUserRole] = useState<UserRole>()
|
||||
const { isUserLoggedIn, decodedIdToken } = useOidc();
|
||||
|
||||
@@ -24,5 +24,7 @@ export const useUserRole = (): UserRole | undefined => {
|
||||
}
|
||||
}, [decodedIdToken, isUserLoggedIn])
|
||||
|
||||
return userRole
|
||||
return {
|
||||
userRole
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,10 @@ const httpClient: AxiosInstance = axios.create(config)
|
||||
|
||||
httpClient.interceptors.request.use(async (config) => {
|
||||
const oidc = await getOidc();
|
||||
console.log('blah')
|
||||
|
||||
if (oidc.isUserLoggedIn) {
|
||||
const { accessToken } = await oidc.getTokens();
|
||||
console.log(accessToken)
|
||||
|
||||
config.headers.Authorization = `Bearer ${accessToken}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
@import "tailwindcss";
|
||||
@source "../node_modules/@noahspan/noahspan-components/dist/**/*.{js,ts,jsx,tsx}";
|
||||
|
||||
@plugin "daisyui" {
|
||||
themes: lofi --default;
|
||||
}
|
||||
|
||||
@plugin "@tailwindcss/typography";
|
||||
|
||||
body {
|
||||
@apply bg-base-300;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface Alert {
|
||||
severity: 'success' | 'error' | 'info' | 'warning';
|
||||
severity: 'danger' | 'default' | 'success' | 'warning';
|
||||
message: string;
|
||||
}
|
||||
@@ -2,39 +2,20 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App.tsx';
|
||||
import AppContextProvider from './context/appContext/AppContextProvider.tsx';
|
||||
import LogbookContextProvider from './context/logbookContext/LogbookContextProvider.tsx'
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import './index.css';
|
||||
import { OidcProvider } from './auth/oidcConfig.ts';
|
||||
// import { AuthProvider as OidcProvider } from 'react-oidc-context';
|
||||
// import { oidcConfig } from './auth/oidcConfig.ts';
|
||||
|
||||
|
||||
// const msalInstance: PublicClientApplication = new PublicClientApplication(msalConfig);
|
||||
|
||||
// msalInstance.initialize().then(() => {
|
||||
// const accounts = msalInstance.getAllAccounts();
|
||||
|
||||
// if (accounts.length > 0) {
|
||||
// msalInstance.setActiveAccount(accounts[0]);
|
||||
// }
|
||||
|
||||
// msalInstance.addEventCallback((event: EventMessage) => {
|
||||
// if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) {
|
||||
// const payload = event.payload as AuthenticationResult;
|
||||
// const account = payload.account;
|
||||
// msalInstance.setActiveAccount(account);
|
||||
// }
|
||||
// });
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<OidcProvider>
|
||||
<AppContextProvider>
|
||||
<LogbookContextProvider>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</LogbookContextProvider>
|
||||
</AppContextProvider>
|
||||
</OidcProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
// })
|
||||
);
|
||||
|
||||
10
client/src/styles.css
Normal file
10
client/src/styles.css
Normal file
@@ -0,0 +1,10 @@
|
||||
@import "tailwindcss";
|
||||
@plugin './hero.ts';
|
||||
/* Note: You may need to change the path to fit your project structure */
|
||||
@source '../../node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}';
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
@plugin "@tailwindcss/typography";
|
||||
|
||||
/* body {
|
||||
@apply bg-base-300;
|
||||
} */
|
||||
10
client/src/tanstack.d.ts
vendored
Normal file
10
client/src/tanstack.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import '@tanstack/react-table';
|
||||
|
||||
/* eslint-disable */
|
||||
declare module '@tanstack/react-table' {
|
||||
interface ColumnMeta<TData extends RowData, TValue> {
|
||||
align?: 'left' | 'center' | 'right';
|
||||
headerAlign?: 'left' | 'center' | 'right';
|
||||
}
|
||||
}
|
||||
/* eslint-enable */
|
||||
Binary file not shown.
3515
package-lock.json
generated
3515
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,9 @@
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"daisyui": "^5.1.10"
|
||||
"@heroui/react": "^2.8.5",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"daisyui": "^5.1.10",
|
||||
"framer-motion": "^12.23.24"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user