adding load more button to flights page (#114)

This commit was merged in pull request #114.
This commit is contained in:
2026-03-21 10:43:44 -05:00
committed by GitHub
parent 477469831f
commit a9abb5863c
9 changed files with 473 additions and 69 deletions

View File

@@ -14,6 +14,7 @@ describe('LogController', () => {
delete: jest.fn(), delete: jest.fn(),
find: jest.fn(), find: jest.fn(),
findLogsWithCount: jest.fn(), findLogsWithCount: jest.fn(),
findLogsWithTracks: jest.fn(),
update: jest.fn() update: jest.fn()
} }
@@ -38,18 +39,41 @@ describe('LogController', () => {
}) })
it('find => should find a log by id', async () => { it('find => should find a log by id', async () => {
const id: string = '95834f84-0a02-44d3-884e-a20237adeca0';
const log = { const log = {
id: '95834f84-0a02-44d3-884e-a20237adeca0',
pilotId: '7c4bae6b-9ec4-469e-8e16-fcbf0b940936',
date: new Date('2026-02-21'),
aircraftMakeModel: 'Cessna 172M',
aircraftIdentity: 'N12345',
routeFrom: 'KMSP',
routeTo: 'KMSP',
durationOfFlight: 1,
singleEngineLand: 1,
simulatorAtd: null,
landingsDay: 1,
landingsNight: null,
groundTrainingReceived: null,
flightTrainingReceived: null,
crossCountry: 1,
night: null,
solo: 1,
pilotInCommand: 1,
instrumentActual: null,
instrumentSimulated: null,
instrumentApproaches: null,
instrumentHolds: null,
instrumentNavTrack: null,
notes: 'This is a test',
tracks: []
} as LogEntity } as LogEntity
jest.spyOn(mockLogService, 'find').mockReturnValue(log); jest.spyOn(mockLogService, 'find').mockReturnValue(log);
const result = await controller.find(id); const result = await controller.find(log.id);
expect(result).toEqual(log); expect(result).toEqual(log);
expect(mockLogService.find).toHaveBeenCalled(); expect(mockLogService.find).toHaveBeenCalled();
expect(mockLogService.find).toHaveBeenCalledWith(id); expect(mockLogService.find).toHaveBeenCalledWith(log.id);
}) })
it('find => should fail to find a log by id', async () => { it('find => should fail to find a log by id', async () => {
@@ -69,18 +93,60 @@ describe('LogController', () => {
} }
}) })
it('findLogsWithCount => should find all logs', async () => { it('findLogsWithCount => should find logs with count', async () => {
const id: string = '95834f84-0a02-44d3-884e-a20237adeca0';
const log = { const log = {
id: '95834f84-0a02-44d3-884e-a20237adeca0',
pilotId: '7c4bae6b-9ec4-469e-8e16-fcbf0b940936',
date: new Date('2026-02-21'),
aircraftMakeModel: 'Cessna 172M',
aircraftIdentity: 'N12345',
routeFrom: 'KMSP',
routeTo: 'KMSP',
durationOfFlight: 1,
singleEngineLand: 1,
simulatorAtd: null,
landingsDay: 1,
landingsNight: null,
groundTrainingReceived: null,
flightTrainingReceived: null,
crossCountry: 1,
night: null,
solo: 1,
pilotInCommand: 1,
instrumentActual: null,
instrumentSimulated: null,
instrumentApproaches: null,
instrumentHolds: null,
instrumentNavTrack: null,
notes: 'This is a test',
tracks: [
{
"id": "4645ce0c-6fd7-432c-8089-777a7139cf7e",
"url": "http://127.0.0.1:10000/devstoreaccount1/tracks/0e7a641a-0264-47f0-88bf-7c296c842d8b/FlightAware_N70392_KANE_KONA_20250628.kml",
"order": 1
},
{
"id": "5ce26898-a18b-46f4-b989-3fb7acdace08",
"url": "http://127.0.0.1:10000/devstoreaccount1/tracks/0e7a641a-0264-47f0-88bf-7c296c842d8b/FlightAware_N70392_KONA_KANE_20250628.kml",
"order": 2
}
]
} as LogEntity; } as LogEntity;
const logs = [log] const logs = [log]
const count = 1
const morePages = false
jest.spyOn(mockLogService, 'findLogsWithCount').mockReturnValue(logs); jest.spyOn(mockLogService, 'findLogsWithCount').mockReturnValue({
entities: logs,
total: count,
hasNextPage: morePages
});
const result = await controller.findLogsWithCount(); const {entities, total, hasNextPage} = await controller.findLogsWithCount();
expect(result).toEqual(logs); expect(entities).toEqual(logs);
expect(total).toEqual(count);
expect(hasNextPage).toEqual(morePages)
expect(mockLogService.findLogsWithCount).toHaveBeenCalled(); expect(mockLogService.findLogsWithCount).toHaveBeenCalled();
}) })
@@ -98,13 +164,130 @@ describe('LogController', () => {
} }
}) })
it('findLogsWithTracks => should find logs with tracks', async () => {
const log = {
id: '95834f84-0a02-44d3-884e-a20237adeca0',
pilotId: '7c4bae6b-9ec4-469e-8e16-fcbf0b940936',
date: new Date('2026-02-21'),
aircraftMakeModel: 'Cessna 172M',
aircraftIdentity: 'N12345',
routeFrom: 'KMSP',
routeTo: 'KMSP',
durationOfFlight: 1,
singleEngineLand: 1,
simulatorAtd: null,
landingsDay: 1,
landingsNight: null,
groundTrainingReceived: null,
flightTrainingReceived: null,
crossCountry: 1,
night: null,
solo: 1,
pilotInCommand: 1,
instrumentActual: null,
instrumentSimulated: null,
instrumentApproaches: null,
instrumentHolds: null,
instrumentNavTrack: null,
notes: 'This is a test',
tracks: [
{
"id": "4645ce0c-6fd7-432c-8089-777a7139cf7e",
"url": "http://127.0.0.1:10000/devstoreaccount1/tracks/0e7a641a-0264-47f0-88bf-7c296c842d8b/FlightAware_N70392_KANE_KONA_20250628.kml",
"order": 1
},
{
"id": "5ce26898-a18b-46f4-b989-3fb7acdace08",
"url": "http://127.0.0.1:10000/devstoreaccount1/tracks/0e7a641a-0264-47f0-88bf-7c296c842d8b/FlightAware_N70392_KONA_KANE_20250628.kml",
"order": 2
}
]
} as LogEntity;
const logs = [log]
const count = 1
const morePages = false
jest.spyOn(mockLogService, 'findLogsWithTracks').mockReturnValue({
entities: logs,
total: count,
hasNextPage: morePages
});
const {entities, total, hasNextPage} = await controller.findLogsWithTracks();
expect(entities).toEqual(logs);
expect(total).toEqual(count);
expect(hasNextPage).toEqual(morePages)
expect(mockLogService.findLogsWithTracks).toHaveBeenCalled();
})
it('findLogsWithCount => should fail to find logs with tracks', async () => {
jest.spyOn(mockLogService, 'findLogsWithTracks').mockRejectedValue(new Error('Logs not found'))
try {
await controller.findLogsWithTracks();
fail('findLogsWithTracks did not throw error');
} catch (error) {
expect(error).toBeInstanceOf(Error);
expect(mockLogService.findLogsWithTracks).toHaveBeenCalled();
expect(mockLogService.findLogsWithTracks).rejects.toThrow('Logs not found')
}
})
it('create => should create a new log', async () => { it('create => should create a new log', async () => {
const logDto = { const logDto = {
pilotId: '7c4bae6b-9ec4-469e-8e16-fcbf0b940936',
date: new Date('2026-02-21'),
aircraftMakeModel: 'Cessna 172M',
aircraftIdentity: 'N12345',
routeFrom: 'KMSP',
routeTo: 'KMSP',
durationOfFlight: 1,
singleEngineLand: 1,
simulatorAtd: null,
landingsDay: 1,
landingsNight: null,
groundTrainingReceived: null,
flightTrainingReceived: null,
crossCountry: 1,
night: null,
solo: 1,
pilotInCommand: 1,
instrumentActual: null,
instrumentSimulated: null,
instrumentApproaches: null,
instrumentHolds: null,
instrumentNavTrack: null,
notes: 'This is a test'
} as LogDto; } as LogDto;
const log = { const log = {
id: '95834f84-0a02-44d3-884e-a20237adeca0',
pilotId: '7c4bae6b-9ec4-469e-8e16-fcbf0b940936',
date: new Date('2026-02-21'),
aircraftMakeModel: 'Cessna 172M',
aircraftIdentity: 'N12345',
routeFrom: 'KMSP',
routeTo: 'KMSP',
durationOfFlight: 1,
singleEngineLand: 1,
simulatorAtd: null,
landingsDay: 1,
landingsNight: null,
groundTrainingReceived: null,
flightTrainingReceived: null,
crossCountry: 1,
night: null,
solo: 1,
pilotInCommand: 1,
instrumentActual: null,
instrumentSimulated: null,
instrumentApproaches: null,
instrumentHolds: null,
instrumentNavTrack: null,
notes: 'This is a test',
tracks: []
} as LogEntity; } as LogEntity;
jest.spyOn(mockLogService, 'create').mockReturnValue(log); jest.spyOn(mockLogService, 'create').mockReturnValue(log);
@@ -116,9 +299,31 @@ describe('LogController', () => {
expect(result).toEqual(log); expect(result).toEqual(log);
}) })
it('create => should failt to create a new log', async () => { it('create => should fail to create a new log', async () => {
const logDto = { const logDto = {
pilotId: '7c4bae6b-9ec4-469e-8e16-fcbf0b940936',
date: new Date('2026-02-21'),
aircraftMakeModel: 'Cessna 172M',
aircraftIdentity: 'N12345',
routeFrom: 'KMSP',
routeTo: 'KMSP',
durationOfFlight: 1,
singleEngineLand: 1,
simulatorAtd: null,
landingsDay: 1,
landingsNight: null,
groundTrainingReceived: null,
flightTrainingReceived: null,
crossCountry: 1,
night: null,
solo: 1,
pilotInCommand: 1,
instrumentActual: null,
instrumentSimulated: null,
instrumentApproaches: null,
instrumentHolds: null,
instrumentNavTrack: null,
notes: 'This is a test'
} as LogDto; } as LogDto;
jest.spyOn(mockLogService, 'create').mockRejectedValue(new Error('Log failed to create')) jest.spyOn(mockLogService, 'create').mockRejectedValue(new Error('Log failed to create'))
@@ -138,7 +343,29 @@ describe('LogController', () => {
it('update => should update an existing log', async () => { it('update => should update an existing log', async () => {
const id: string = '95834f84-0a02-44d3-884e-a20237adeca0'; const id: string = '95834f84-0a02-44d3-884e-a20237adeca0';
const logDto = { const logDto = {
pilotId: '7c4bae6b-9ec4-469e-8e16-fcbf0b940936',
date: new Date('2026-02-21'),
aircraftMakeModel: 'Cessna 172M',
aircraftIdentity: 'N12345',
routeFrom: 'KMSP',
routeTo: 'KMSP',
durationOfFlight: 1,
singleEngineLand: 1,
simulatorAtd: null,
landingsDay: 1,
landingsNight: null,
groundTrainingReceived: null,
flightTrainingReceived: null,
crossCountry: 1,
night: null,
solo: 1,
pilotInCommand: 1,
instrumentActual: null,
instrumentSimulated: null,
instrumentApproaches: null,
instrumentHolds: null,
instrumentNavTrack: null,
notes: 'This is a test'
} as LogDto } as LogDto
jest.spyOn(mockLogService, 'update').mockReturnValue(logDto); jest.spyOn(mockLogService, 'update').mockReturnValue(logDto);
@@ -153,7 +380,29 @@ describe('LogController', () => {
it('update => should fail to update an exising log', async () => { it('update => should fail to update an exising log', async () => {
const id: string = '95834f84-0a02-44d3-884e-a20237adeca0'; const id: string = '95834f84-0a02-44d3-884e-a20237adeca0';
const logDto = { const logDto = {
pilotId: '7c4bae6b-9ec4-469e-8e16-fcbf0b940936',
date: new Date('2026-02-21'),
aircraftMakeModel: 'Cessna 172M',
aircraftIdentity: 'N12345',
routeFrom: 'KMSP',
routeTo: 'KMSP',
durationOfFlight: 1,
singleEngineLand: 1,
simulatorAtd: null,
landingsDay: 1,
landingsNight: null,
groundTrainingReceived: null,
flightTrainingReceived: null,
crossCountry: 1,
night: null,
solo: 1,
pilotInCommand: 1,
instrumentActual: null,
instrumentSimulated: null,
instrumentApproaches: null,
instrumentHolds: null,
instrumentNavTrack: null,
notes: 'This is a test'
} as LogDto; } as LogDto;
jest.spyOn(mockLogService, 'update').mockRejectedValue(new Error('Log failed to update')) jest.spyOn(mockLogService, 'update').mockRejectedValue(new Error('Log failed to update'))

View File

@@ -31,6 +31,31 @@ export class LogController {
private readonly logService: LogService private readonly logService: LogService
) {} ) {}
@Get()
@Public()
async findLogsWithCount(@Query('skip') skip?, @Query('take') take?: number,): Promise<{ entities: LogEntity[], total: number, hasNextPage: boolean }> {
try {
return await this.logService.findLogsWithCount(skip, take)
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode)
}
}
@Get('tracks')
@Public()
async findLogsWithTracks(@Query('skip') skip?, @Query('take') take?: number,): Promise<{ entities: LogEntity[], total: number, hasNextPage: boolean }> {
try {
const result = await this.logService.findLogsWithTracks(skip, take)
console.log(result);
return result
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Get(':id') @Get(':id')
@Public() @Public()
@@ -46,19 +71,6 @@ export class LogController {
} }
} }
@Get()
@Public()
async findLogsWithCount(@Query('skip') skip?, @Query('take') take?: number,): Promise<{ entities: LogEntity[], total: number, hasNextPage: boolean }> {
try {
return await this.logService.findLogsWithCount(skip, take)
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode)
}
}
@Post() @Post()
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
async create(@Body() logDto: LogDto) { async create(@Body() logDto: LogDto) {

View File

@@ -15,7 +15,15 @@ describe('LogService', () => {
deleteFolder: jest.fn() deleteFolder: jest.fn()
} }
const mockQueryBuilder = {
createQueryBuilder: jest.fn().mockReturnThis(),
innerJoinAndSelect: jest.fn().mockReturnThis(),
orderBy: jest.fn().mockReturnThis(),
getManyAndCount: jest.fn()
}
const mockLogRepository = { const mockLogRepository = {
createQueryBuilder: jest.fn().mockReturnValue(mockQueryBuilder),
delete: jest.fn(), delete: jest.fn(),
findAndCount: jest.fn(), findAndCount: jest.fn(),
findOne: jest.fn(), findOne: jest.fn(),
@@ -59,7 +67,7 @@ describe('LogService', () => {
expect(service).toBeDefined(); expect(service).toBeDefined();
}) })
it('create => shoule create a log entry', async () => { it('create => should create a log entry', async () => {
const logDto = { const logDto = {
pilotId: '7c4bae6b-9ec4-469e-8e16-fcbf0b940936', pilotId: '7c4bae6b-9ec4-469e-8e16-fcbf0b940936',
date: new Date('2026-02-21'), date: new Date('2026-02-21'),
@@ -207,7 +215,18 @@ describe('LogService', () => {
instrumentHolds: null, instrumentHolds: null,
instrumentNavTrack: null, instrumentNavTrack: null,
notes: 'This is a test', notes: 'This is a test',
tracks: [] tracks: [
{
"id": "4645ce0c-6fd7-432c-8089-777a7139cf7e",
"url": "http://127.0.0.1:10000/devstoreaccount1/tracks/0e7a641a-0264-47f0-88bf-7c296c842d8b/FlightAware_N70392_KANE_KONA_20250628.kml",
"order": 1
},
{
"id": "5ce26898-a18b-46f4-b989-3fb7acdace08",
"url": "http://127.0.0.1:10000/devstoreaccount1/tracks/0e7a641a-0264-47f0-88bf-7c296c842d8b/FlightAware_N70392_KONA_KANE_20250628.kml",
"order": 2
}
]
} as LogEntity; } as LogEntity;
const logs = [log]; const logs = [log];
const count = 1 const count = 1
@@ -223,6 +242,59 @@ describe('LogService', () => {
expect(mockLogRepository.findAndCount).toHaveBeenCalled(); expect(mockLogRepository.findAndCount).toHaveBeenCalled();
}) })
it('findLogsWithTracks => should find log entries with tracks', async () => {
const log = {
id: 'd685f1ca-28e0-40b9-8713-74467db12965',
pilotId: '7c4bae6b-9ec4-469e-8e16-fcbf0b940936',
date: new Date('2026-02-21'),
aircraftMakeModel: 'Cessna 172M',
aircraftIdentity: 'N12345',
routeFrom: 'KMSP',
routeTo: 'KMSP',
durationOfFlight: 1,
singleEngineLand: 1,
simulatorAtd: null,
landingsDay: 1,
landingsNight: null,
groundTrainingReceived: null,
flightTrainingReceived: null,
crossCountry: 1,
night: null,
solo: 1,
pilotInCommand: 1,
instrumentActual: null,
instrumentSimulated: null,
instrumentApproaches: null,
instrumentHolds: null,
instrumentNavTrack: null,
notes: 'This is a test',
tracks: [
{
"id": "4645ce0c-6fd7-432c-8089-777a7139cf7e",
"url": "http://127.0.0.1:10000/devstoreaccount1/tracks/0e7a641a-0264-47f0-88bf-7c296c842d8b/FlightAware_N70392_KANE_KONA_20250628.kml",
"order": 1
},
{
"id": "5ce26898-a18b-46f4-b989-3fb7acdace08",
"url": "http://127.0.0.1:10000/devstoreaccount1/tracks/0e7a641a-0264-47f0-88bf-7c296c842d8b/FlightAware_N70392_KONA_KANE_20250628.kml",
"order": 2
}
]
} as LogEntity;
const logs = [log];
const count = 1
const morePages = false
jest.spyOn(mockQueryBuilder, 'getManyAndCount').mockResolvedValue([logs, count, morePages])
const {entities, total, hasNextPage} = await service.findLogsWithTracks();
expect(entities).toEqual(logs);
expect(count).toEqual(total);
expect(hasNextPage).toEqual(morePages);
expect(mockLogRepository.findAndCount).toHaveBeenCalled();
})
it('update => should update a log entry', async () => { it('update => should update a log entry', async () => {
const id: string = 'd685f1ca-28e0-40b9-8713-74467db12965'; const id: string = 'd685f1ca-28e0-40b9-8713-74467db12965';
const logDto = { const logDto = {

View File

@@ -30,7 +30,22 @@ export class LogService {
take, take,
skip, skip,
relations: ['pilot', 'tracks'] relations: ['pilot', 'tracks']
}) })
return {
entities,
total,
hasNextPage: skip + take < total
}
}
async findLogsWithTracks(skip?: number, take?: number): Promise<{entities: LogEntity[], total: number, hasNextPage: boolean}> {
const [entities, total] = await this.logRepository
.createQueryBuilder('logs')
.innerJoinAndSelect('logs.tracks', 'track')
.innerJoinAndSelect('logs.pilot', 'pilot')
.orderBy('date')
.getManyAndCount();
return { return {
entities, entities,

View File

@@ -5,39 +5,72 @@ import { LogbookEntry } from "../logbook/LogbookEntry.interface";
import { initialState, reducer } from "./reducer"; import { initialState, reducer } from "./reducer";
import { useOidc } from "../../auth/oidcConfig"; import { useOidc } from "../../auth/oidcConfig";
import Alert from "../alert/Alert"; import Alert from "../alert/Alert";
import { AxiosError, AxiosResponse } from "axios";
import httpClient from "../../httpClient/httpClient";
import { useBreakpoints } from "../../hooks/useBreakpoints/UseBreakpoints";
import { ScreenSize } from "../../enums/screenSize";
const Flights = () => { const Flights = () => {
const [state, dispatch] = useReducer(reducer, initialState); const [state, dispatch] = useReducer(reducer, initialState);
const { logs, logsLoading } = useLogs();
const { isUserLoggedIn } = useOidc(); const { isUserLoggedIn } = useOidc();
const { screenSize } = useBreakpoints();
const getFlights = async (pageIndex: number, pageSize: number) => {
try {
const response: AxiosResponse = await httpClient.get(`api/logs/tracks`, {
params: {
skip: pageIndex,
take: pageSize
}
});
const flights: LogbookEntry[] = response.data.entities;
const hasMore: boolean = response.data.hasNextPage;
const total: number = response.data.total;
if (flights.length > 0) {
const newFlights: LogbookEntry[] = [...state.flights, ...flights]
const newPageIndex: number = state.pageIndex + flights.length;
dispatch({ type: 'SET_FLIGHTS', payload: { flights: newFlights, hasMoreFlights: hasMore, pageIndex: newPageIndex, totalFlights: total }})
if (!isUserLoggedIn && flights.length >= 5) {
dispatch({ type: 'SET_ALERT', payload: { severity: 'info', message: 'A limited number of flights displayed. Sign in to view all flights.'}})
} else {
dispatch({ type: 'SET_ALERT', payload: undefined })
}
} else {
dispatch({ type: 'SET_ALERT', payload: { severity: 'info', message: 'No flights found' }})
}
} catch (error) {
const axiosError = error as AxiosError;
dispatch({
type: 'SET_ALERT',
payload: { severity: 'error', message: `Loading of flights failed with the following message: ${axiosError.message}`}
});
} finally {
dispatch({ type: 'SET_IS_LOADING', payload: false })
}
}
const loadMore = () => {
getFlights(state.pageIndex, state.pageSize)
}
useEffect(() => { useEffect(() => {
getFlights(state.pageIndex, state.pageSize);
}, [])
const flights: LogbookEntry[] | undefined = logs?.filter((log: LogbookEntry) => { useEffect(() => {
if (log.tracks && log.tracks.length > 0) { console.log(screenSize)
return log; }, [screenSize])
}
})
if (flights && flights.length > 0) {
dispatch({ type: 'SET_FLIGHTS', payload: flights})
if (!isUserLoggedIn && flights.length >= 5) {
dispatch({ type: 'SET_ALERT', payload: { severity: 'info', message: 'A limited number of flights displayed. Sign in to view all flights.'}})
} else {
dispatch({ type: 'SET_ALERT', payload: undefined })
}
} else {
dispatch({ type: 'SET_ALERT', payload: { severity: 'info', message: 'No flights found' }})
}
}, [logs])
return ( return (
<div className='max-w-screen-lg mx-auto'> <div className={`max-w-screen-lg mx-auto ${screenSize === ScreenSize.SM || screenSize === ScreenSize.MD? 'mr-4 ml-4' : ''}`}>
<div className='prose mt-5 mb-5'> <div className='prose mt-5 mb-5'>
<h1>Flights</h1> <h1>Flights</h1>
</div> </div>
{!logsLoading && state.alert && ( {!state.isLoading && state.alert && (
<div> <div>
<Alert <Alert
className='mb-5' className='mb-5'
@@ -50,14 +83,21 @@ const Flights = () => {
</Alert> </Alert>
</div> </div>
)} )}
{!logsLoading && {!state.isLoading &&
<div> <div>
<LogbookCard logs={state.flights} mode='flights' /> <div>
<LogbookCard logs={state.flights} mode='flights' />
</div>
{state.hasMoreFlights &&
<div className='flex items-center justify-center mb-5'>
<button className='btn btn-link' onClick={loadMore}>Load more</button>
</div>
}
</div> </div>
} }
{logsLoading && [...Array(5)].map((_element, index) => { {state.isLoading && [...Array(5)].map((_element, index) => {
return ( return (
<div className='card bg-base-100 border border-base-300 p-2 mb-7'> <div className='card bg-base-100 border border-base-300 p-2 mb-5'>
<div className='card-body' key={index}> <div className='card-body' key={index}>
<div className='skeleton h-10 w-[150px]' /> <div className='skeleton h-10 w-[150px]' />
<div className='skeleton h-[350px]' /> <div className='skeleton h-[350px]' />

View File

@@ -5,4 +5,8 @@ export interface FlightsState {
alert: Alert | undefined; alert: Alert | undefined;
flights: LogbookEntry[]; flights: LogbookEntry[];
isLoading: boolean; isLoading: boolean;
hasMoreFlights: boolean;
pageIndex: number;
pageSize: number
totalFlights: number;
} }

View File

@@ -5,13 +5,17 @@ import { FlightsState } from "./FlightsState.interface";
type Action = type Action =
| { type: 'SET_ALERT'; payload: Alert | undefined } | { type: 'SET_ALERT'; payload: Alert | undefined }
| { type: 'SET_FLIGHTS'; payload: LogbookEntry[] } | { type: 'SET_FLIGHTS'; payload: { flights: LogbookEntry[], hasMoreFlights: boolean, pageIndex: number, totalFlights: number } }
| { type: 'SET_IS_LOADING'; payload: boolean } | { type: 'SET_IS_LOADING'; payload: boolean }
export const initialState: FlightsState = { export const initialState: FlightsState = {
alert: undefined, alert: undefined,
flights: [], flights: [],
isLoading: true isLoading: true,
hasMoreFlights: true,
pageIndex: 0,
pageSize: 5,
totalFlights: 0
} }
export const reducer = ( export const reducer = (
@@ -28,7 +32,10 @@ export const reducer = (
case 'SET_FLIGHTS': { case 'SET_FLIGHTS': {
return { return {
...state, ...state,
flights: action.payload flights: action.payload.flights,
hasMoreFlights: action.payload.hasMoreFlights,
pageIndex: action.payload.pageIndex,
totalFlights: action.payload.totalFlights
} }
} }
case 'SET_IS_LOADING': { case 'SET_IS_LOADING': {

View File

@@ -1,7 +1,12 @@
import { LogbookCardProps } from "./LogbookCardProps.interface"; import { LogbookCardProps } from "./LogbookCardProps.interface";
import TrackMap from "../trackMap/TrackMap"; import TrackMap from "../trackMap/TrackMap";
import { useBreakpoints } from "../../hooks/useBreakpoints/UseBreakpoints";
import { ScreenSize } from "../../enums/screenSize";
import { useEffect } from "react";
const LogbookCard = ({ logs, mode, onDelete, onOpenCloseForm }: LogbookCardProps) => { const LogbookCard = ({ logs, mode, onDelete, onOpenCloseForm }: LogbookCardProps) => {
const { screenSize } = useBreakpoints();
return ( return (
<div> <div>
{logs.map((log) => { {logs.map((log) => {
@@ -9,8 +14,8 @@ const LogbookCard = ({ logs, mode, onDelete, onOpenCloseForm }: LogbookCardProps
const formattedDate: string = `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`; const formattedDate: string = `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`;
return ( return (
<div className='card bg-base-100 border border-base-300 p-2'> <div className='card bg-base-100 border border-base-300 p-2 mb-5'>
<div className='card-body' key={log.id}> <div className={`card-body ${screenSize === ScreenSize.SM || screenSize === ScreenSize.MD ? 'p-2' : ''}`} key={log.id}>
<h2 className='card-title font-bold text-2xl'>{formattedDate}</h2> <h2 className='card-title font-bold text-2xl'>{formattedDate}</h2>
<div> <div>
{mode === 'flights' && log.tracks && log.tracks.length > 0 && {mode === 'flights' && log.tracks && log.tracks.length > 0 &&
@@ -24,10 +29,10 @@ const LogbookCard = ({ logs, mode, onDelete, onOpenCloseForm }: LogbookCardProps
} }
</div> </div>
<div className='collapse collapse-arrow bg-base-100 border-base-300 border'> <div className='collapse collapse-arrow bg-base-100 border-base-300 border'>
<input type='radio' name='details-accordion' /> <input type='checkbox' />
<div className='collapse-title font-semibold'>Details</div> <div className='collapse-title font-semibold'>Details</div>
<div className='collapse-content'> <div className='collapse-content'>
<div className='grid grid-cols-12 gap-3 mr-[30%] ml-[30%] mt-4 mb-4'> <div className={`grid grid-cols-12 gap-3 ${screenSize === ScreenSize.SM || screenSize === ScreenSize.MD ? 'mr-[5%] ml-[5%]' : 'mr-[30%] ml-[30%]'} mt-4 mb-4`}>
<div className='col-span-6 font-bold'> <div className='col-span-6 font-bold'>
<span>Aircraft Make and Model</span> <span>Aircraft Make and Model</span>
</div> </div>

8
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "@noahspan/flying", "name": "@noahspan/flying",
"version": "2.0.0-alpha-3", "version": "2.0.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@noahspan/flying", "name": "@noahspan/flying",
"version": "2.0.0-alpha-3", "version": "2.0.2",
"workspaces": [ "workspaces": [
"api", "api",
"client", "client",
@@ -26,7 +26,7 @@
} }
}, },
"api": { "api": {
"version": "2.0.0-alpha-3", "version": "2.0.2",
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"@azure/storage-blob": "^12.27.0", "@azure/storage-blob": "^12.27.0",
@@ -81,7 +81,7 @@
} }
}, },
"client": { "client": {
"version": "2.0.0-alpha-3", "version": "2.0.2",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^7.1.0", "@fortawesome/fontawesome-svg-core": "^7.1.0",
"@fortawesome/free-solid-svg-icons": "^7.1.0", "@fortawesome/free-solid-svg-icons": "^7.1.0",