Feature/6 pilots delete #42

Merged
noahspannbauer merged 4 commits from feature/6-pilots---delete into main 2025-02-18 19:16:05 -05:00
4 changed files with 143 additions and 103 deletions
Showing only changes of commit 01127e6f18 - Show all commits

View File

@@ -4,9 +4,23 @@ import { PilotService } from './pilot.service';
import { AzureTableStorageModule } from '@noahspan/azure-database';
import { Pilot } from './pilot.entity';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { Log } from 'src/log/log.entity';
@Module({
imports: [
AzureTableStorageModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => {
return {
connectionString: configService.get<string>('azureStorageConnectionString')
};
},
inject: [ConfigService]
}),
AzureTableStorageModule.forFeature(Log, {
createTableIfNotExists: false,
table: 'logs'
}),
AzureTableStorageModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => {
@@ -19,7 +33,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
AzureTableStorageModule.forFeature(Pilot, {
createTableIfNotExists: false,
table: 'pilots'
}),
})
],
controllers: [PilotController],
providers: [PilotService]

View File

@@ -1,12 +1,14 @@
import { InjectRepository, Repository } from '@noahspan/azure-database';
import { Injectable } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import { Pilot } from './pilot.entity';
import { Log } from 'src/log/log.entity';
import { LogService } from 'src/log/log.service';
@Injectable()
export class PilotService {
constructor(
@InjectRepository(Pilot)
private readonly pilotRepository: Repository<Pilot>
@InjectRepository(Pilot) private readonly pilotRepository: Repository<Pilot>,
@InjectRepository(Log) private readonly logRepository: Repository<Log>
) {}
async find(partitionKey: string, rowKey: string): Promise<Pilot> {
@@ -35,6 +37,18 @@ export class PilotService {
}
async delete(partitionKey: string, rowKey: string): Promise<void> {
const pilotLogs: Log[] = await this.logRepository.findAll({
queryOptions: {
filter: `pilotId eq '${rowKey}'`
}
})
for (const pilotLog of pilotLogs) {
await this.logRepository.delete(pilotLog.partitionKey, pilotLog.rowKey);
}
await this.pilotRepository.delete(partitionKey, rowKey);
return
}
}

View File

@@ -375,6 +375,7 @@ const Logbook: React.FC<unknown> = () => {
<Typography variant="h4">Logbook</Typography>
</Grid>
<Grid display="flex" justifyContent="right" size={1}>
{isAuthenticated &&
<Button
onClick={() => onOpenCloseEntryForm(FormMode.ADD)}
startIcon={<PlusIcon />}
@@ -383,6 +384,7 @@ const Logbook: React.FC<unknown> = () => {
>
Add Entry
</Button>
}
</Grid>
{!state.isLoading && state.error && (
<Grid display="flex" justifyContent="center" size={12}>

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { useEffect, useReducer, useState } from 'react';
import PilotForm from '../pilotForm/PilotForm';
import {
Box,
@@ -19,26 +19,20 @@ import {
Typography
} from '@noahspan/noahspan-components';
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
import { AxiosInstance, AxiosResponse } from 'axios';
import { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
import { useAccessToken } from '../../hooks/accessToken/UseAcessToken';
import { useIsAuthenticated } from '@azure/msal-react';
import { FormMode } from '../../enums/formMode';
type Pilot = {
partitionKey: string;
rowKey: string;
id: string;
name: string;
};
import { Pilot } from './Pilot.interface';
import { initialState, reducer } from './reducer';
import ActionMenu from '../actionMenu/ActionMenu';
import ConfirmationDialog from '../confirmationDialog/ConfirmationDialog';
const Pilots: React.FC<unknown> = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const httpClient: AxiosInstance = useHttpClient();
const isAuthenticated = useIsAuthenticated();
const { getAccessToken } = useAccessToken();
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [pilotFormMode, setPilotFormMode] = useState<FormMode>(FormMode.CANCEL);
const [selectedPilotId, setSelectedPilotId] = useState<string | undefined>();
const [pilots, setPilots] = useState<Pilot[]>([]);
const getPilots = async (): Promise<Pilot[]> => {
try {
@@ -62,76 +56,70 @@ const Pilots: React.FC<unknown> = () => {
case FormMode.ADD:
case FormMode.EDIT:
case FormMode.VIEW:
setPilotFormMode(mode);
setSelectedPilotId(pilotId);
setIsDrawerOpen(true);
dispatch({
type: 'SET_OPEN_CLOSE_ENTRY_FORM',
payload: {
formMode: mode,
selectedPilotId: pilotId,
isFormOpen: true
}
})
break;
case FormMode.CANCEL:
const pilots = await getPilots();
dispatch({
type: 'SET_OPEN_CLOSE_ENTRY_FORM',
payload: {
formMode: mode,
selectedPilotId: undefined,
isFormOpen: false
}
})
setPilots(pilots);
setPilotFormMode(mode);
setSelectedPilotId(undefined);
setIsDrawerOpen(false);
break;
}
};
interface ActionMenuProps {
pilotId: string;
const onDeleteEntry = (pilotId: string) => {
dispatch({
type: 'SET_DELETE',
payload: { isConfirmDialogOpen: true, selectedPilotId: pilotId }
});
};
const onConfirmationDialogConfirm = async () => {
try {
dispatch({ type: 'SET_IS_CONFIRMATION_DIALOG_LOADING', payload: true });
const token = await getAccessToken();
const config = isAuthenticated
? { headers: { Authorization: `${token}` } }
: {};
await httpClient.delete(`api/pilots/pilot/${state.selectedPilotId}`, config);
dispatch({
type: 'SET_DELETE',
payload: { isConfirmDialogOpen: false, selectedPilotId: undefined }
});
await getPilots();
} catch (error) {
const axiosError = error as AxiosError;
dispatch({
type: 'SET_ERROR',
payload: `Loading of logbook entries failed with the following message: ${axiosError.message}`
});
} finally {
dispatch({ type: 'SET_IS_CONFIRMATION_DIALOG_LOADING', payload: false });
}
const ActionMenu = ({ pilotId }: ActionMenuProps) => {
const [anchorElAction, setAnchorElAction] = useState<null | HTMLElement>(
null
);
const onOpenActionMenu = (event: React.MouseEvent<HTMLElement>) => {
console.log(event);
setAnchorElAction(event.currentTarget);
};
const onCloseActionMenu = () => {
setAnchorElAction(null);
};
return (
<div>
<IconButton onClick={onOpenActionMenu}>
<EllipsisVerticalIcon size="sm" />
</IconButton>
<Menu
anchorEl={anchorElAction}
keepMounted
open={Boolean(anchorElAction)}
onClose={onCloseActionMenu}
>
<MenuItem
onClick={() => onOpenClosePilotForm(FormMode.EDIT, pilotId)}
>
<ListItemIcon>
<PenIcon size="lg" />
</ListItemIcon>
<ListItemText>Edit</ListItemText>
</MenuItem>
<MenuItem
onClick={() => onOpenClosePilotForm(FormMode.VIEW, pilotId)}
>
<ListItemIcon>
<EyeIcon size="lg" />
</ListItemIcon>
<ListItemText>View</ListItemText>
</MenuItem>
<hr className="my-3" />
<MenuItem>
<ListItemIcon>
<TrashIcon size="lg" />
</ListItemIcon>
<ListItemText>Delete</ListItemText>
</MenuItem>
</Menu>
</div>
);
const onConfirmationDialogCancel = () => {
dispatch({
type: 'SET_DELETE',
payload: { isConfirmDialogOpen: false, selectedPilotId: undefined }
});
};
const columns: ColumnDef<Pilot>[] = [
@@ -141,7 +129,13 @@ const Pilots: React.FC<unknown> = () => {
},
{
header: 'Actions',
cell: (info) => <ActionMenu pilotId={info.row.original.rowKey} />
cell: (info) => (
<ActionMenu
id={info.row.original.rowKey}
onDelete={onDeleteEntry}
onOpenCloseForm={onOpenClosePilotForm}
/>
)
}
];
@@ -150,14 +144,16 @@ const Pilots: React.FC<unknown> = () => {
try {
const pilots = await getPilots();
setPilots(pilots);
dispatch({ type: 'SET_PILOTS', payload: pilots })
} catch (error) {
console.log(error);
}
};
if (isAuthenticated && !state.isFormOpen) {
loadPilots();
}, []);
}
}, [isAuthenticated, state.isFormOpen]);
return (
<Box sx={{ margin: '20px' }}>
@@ -166,6 +162,7 @@ const Pilots: React.FC<unknown> = () => {
<Typography variant="h4">Pilots</Typography>
</Grid>
<Grid display="flex" justifyContent="right" size={1}>
{isAuthenticated &&
<Button
onClick={() => onOpenClosePilotForm(FormMode.ADD)}
startIcon={<PlusIcon />}
@@ -174,17 +171,30 @@ const Pilots: React.FC<unknown> = () => {
>
Add Pilot
</Button>
}
</Grid>
<Grid size={12}>
{pilots.length > 0 && <Table columns={columns} data={pilots} />}
{state.pilots.length > 0 && <Table columns={columns} data={state.pilots} />}
</Grid>
</Grid>
{state.isFormOpen && (
<PilotForm
isDrawerOpen={isDrawerOpen}
mode={pilotFormMode}
isDrawerOpen={state.isFormOpen}
mode={state.formMode}
onOpenClose={(mode) => onOpenClosePilotForm(mode)}
pilotId={selectedPilotId}
pilotId={state.selectedPilotId}
/>
)}
{state.isConfirmDialogOpen && (
<ConfirmationDialog
contentText="Are you sure you want to delete the pilot entry? Deleting a pilot will delete the pilot and delete all logbook entries for the pilot."
isLoading={state.isConfirmDialogLoading}
isOpen={state.isConfirmDialogOpen}
onCancel={onConfirmationDialogCancel}
onConfirm={onConfirmationDialogConfirm}
title="Confirm Delete"
/>
)}
</Box>
);
};