adding pilot delete (#41)
* adding pilot delete * adding pilot delete
This commit was merged in pull request #41.
This commit is contained in:
@@ -4,9 +4,23 @@ import { PilotService } from './pilot.service';
|
|||||||
import { AzureTableStorageModule } from '@noahspan/azure-database';
|
import { AzureTableStorageModule } from '@noahspan/azure-database';
|
||||||
import { Pilot } from './pilot.entity';
|
import { Pilot } from './pilot.entity';
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { Log } from 'src/log/log.entity';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
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({
|
AzureTableStorageModule.forRootAsync({
|
||||||
imports: [ConfigModule],
|
imports: [ConfigModule],
|
||||||
useFactory: async (configService: ConfigService) => {
|
useFactory: async (configService: ConfigService) => {
|
||||||
@@ -19,7 +33,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
|
|||||||
AzureTableStorageModule.forFeature(Pilot, {
|
AzureTableStorageModule.forFeature(Pilot, {
|
||||||
createTableIfNotExists: false,
|
createTableIfNotExists: false,
|
||||||
table: 'pilots'
|
table: 'pilots'
|
||||||
}),
|
})
|
||||||
],
|
],
|
||||||
controllers: [PilotController],
|
controllers: [PilotController],
|
||||||
providers: [PilotService]
|
providers: [PilotService]
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { InjectRepository, Repository } from '@noahspan/azure-database';
|
import { InjectRepository, Repository } from '@noahspan/azure-database';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Pilot } from './pilot.entity';
|
import { Pilot } from './pilot.entity';
|
||||||
|
import { Log } from 'src/log/log.entity';
|
||||||
|
import { LogService } from 'src/log/log.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PilotService {
|
export class PilotService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(Pilot)
|
@InjectRepository(Pilot) private readonly pilotRepository: Repository<Pilot>,
|
||||||
private readonly pilotRepository: Repository<Pilot>
|
@InjectRepository(Log) private readonly logRepository: Repository<Log>
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async find(partitionKey: string, rowKey: string): Promise<Pilot> {
|
async find(partitionKey: string, rowKey: string): Promise<Pilot> {
|
||||||
@@ -35,6 +37,18 @@ export class PilotService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async delete(partitionKey: string, rowKey: string): Promise<void> {
|
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);
|
await this.pilotRepository.delete(partitionKey, rowKey);
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -375,14 +375,16 @@ const Logbook: React.FC<unknown> = () => {
|
|||||||
<Typography variant="h4">Logbook</Typography>
|
<Typography variant="h4">Logbook</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid display="flex" justifyContent="right" size={1}>
|
<Grid display="flex" justifyContent="right" size={1}>
|
||||||
<Button
|
{isAuthenticated &&
|
||||||
onClick={() => onOpenCloseEntryForm(FormMode.ADD)}
|
<Button
|
||||||
startIcon={<PlusIcon />}
|
onClick={() => onOpenCloseEntryForm(FormMode.ADD)}
|
||||||
variant="contained"
|
startIcon={<PlusIcon />}
|
||||||
data-testid="pilot-add-button"
|
variant="contained"
|
||||||
>
|
data-testid="pilot-add-button"
|
||||||
Add Entry
|
>
|
||||||
</Button>
|
Add Entry
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
</Grid>
|
</Grid>
|
||||||
{!state.isLoading && state.error && (
|
{!state.isLoading && state.error && (
|
||||||
<Grid display="flex" justifyContent="center" size={12}>
|
<Grid display="flex" justifyContent="center" size={12}>
|
||||||
|
|||||||
6
app/src/components/pilots/Pilot.interface.ts
Normal file
6
app/src/components/pilots/Pilot.interface.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export interface Pilot {
|
||||||
|
partitionKey: string;
|
||||||
|
rowKey: string;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useReducer, useState } from 'react';
|
||||||
import PilotForm from '../pilotForm/PilotForm';
|
import PilotForm from '../pilotForm/PilotForm';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
@@ -19,26 +19,20 @@ import {
|
|||||||
Typography
|
Typography
|
||||||
} from '@noahspan/noahspan-components';
|
} from '@noahspan/noahspan-components';
|
||||||
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
||||||
import { AxiosInstance, AxiosResponse } from 'axios';
|
import { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
|
||||||
import { useAccessToken } from '../../hooks/accessToken/UseAcessToken';
|
import { useAccessToken } from '../../hooks/accessToken/UseAcessToken';
|
||||||
import { useIsAuthenticated } from '@azure/msal-react';
|
import { useIsAuthenticated } from '@azure/msal-react';
|
||||||
import { FormMode } from '../../enums/formMode';
|
import { FormMode } from '../../enums/formMode';
|
||||||
|
import { Pilot } from './Pilot.interface';
|
||||||
type Pilot = {
|
import { initialState, reducer } from './reducer';
|
||||||
partitionKey: string;
|
import ActionMenu from '../actionMenu/ActionMenu';
|
||||||
rowKey: string;
|
import ConfirmationDialog from '../confirmationDialog/ConfirmationDialog';
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Pilots: React.FC<unknown> = () => {
|
const Pilots: React.FC<unknown> = () => {
|
||||||
|
const [state, dispatch] = useReducer(reducer, initialState);
|
||||||
const httpClient: AxiosInstance = useHttpClient();
|
const httpClient: AxiosInstance = useHttpClient();
|
||||||
const isAuthenticated = useIsAuthenticated();
|
const isAuthenticated = useIsAuthenticated();
|
||||||
const { getAccessToken } = useAccessToken();
|
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[]> => {
|
const getPilots = async (): Promise<Pilot[]> => {
|
||||||
try {
|
try {
|
||||||
@@ -62,76 +56,70 @@ const Pilots: React.FC<unknown> = () => {
|
|||||||
case FormMode.ADD:
|
case FormMode.ADD:
|
||||||
case FormMode.EDIT:
|
case FormMode.EDIT:
|
||||||
case FormMode.VIEW:
|
case FormMode.VIEW:
|
||||||
setPilotFormMode(mode);
|
dispatch({
|
||||||
setSelectedPilotId(pilotId);
|
type: 'SET_OPEN_CLOSE_ENTRY_FORM',
|
||||||
setIsDrawerOpen(true);
|
payload: {
|
||||||
|
formMode: mode,
|
||||||
|
selectedPilotId: pilotId,
|
||||||
|
isFormOpen: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case FormMode.CANCEL:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ActionMenuProps {
|
const onDeleteEntry = (pilotId: string) => {
|
||||||
pilotId: string;
|
dispatch({
|
||||||
}
|
type: 'SET_DELETE',
|
||||||
|
payload: { isConfirmDialogOpen: true, selectedPilotId: pilotId }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const ActionMenu = ({ pilotId }: ActionMenuProps) => {
|
const onConfirmationDialogConfirm = async () => {
|
||||||
const [anchorElAction, setAnchorElAction] = useState<null | HTMLElement>(
|
try {
|
||||||
null
|
dispatch({ type: 'SET_IS_CONFIRMATION_DIALOG_LOADING', payload: true });
|
||||||
);
|
|
||||||
|
|
||||||
const onOpenActionMenu = (event: React.MouseEvent<HTMLElement>) => {
|
const token = await getAccessToken();
|
||||||
console.log(event);
|
const config = isAuthenticated
|
||||||
setAnchorElAction(event.currentTarget);
|
? { headers: { Authorization: `${token}` } }
|
||||||
};
|
: {};
|
||||||
|
|
||||||
const onCloseActionMenu = () => {
|
await httpClient.delete(`api/pilots/pilot/${state.selectedPilotId}`, config);
|
||||||
setAnchorElAction(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
dispatch({
|
||||||
<div>
|
type: 'SET_DELETE',
|
||||||
<IconButton onClick={onOpenActionMenu}>
|
payload: { isConfirmDialogOpen: false, selectedPilotId: undefined }
|
||||||
<EllipsisVerticalIcon size="sm" />
|
});
|
||||||
</IconButton>
|
await getPilots();
|
||||||
<Menu
|
} catch (error) {
|
||||||
anchorEl={anchorElAction}
|
const axiosError = error as AxiosError;
|
||||||
keepMounted
|
|
||||||
open={Boolean(anchorElAction)}
|
dispatch({
|
||||||
onClose={onCloseActionMenu}
|
type: 'SET_ERROR',
|
||||||
>
|
payload: `Loading of logbook entries failed with the following message: ${axiosError.message}`
|
||||||
<MenuItem
|
});
|
||||||
onClick={() => onOpenClosePilotForm(FormMode.EDIT, pilotId)}
|
} finally {
|
||||||
>
|
dispatch({ type: 'SET_IS_CONFIRMATION_DIALOG_LOADING', payload: false });
|
||||||
<ListItemIcon>
|
}
|
||||||
<PenIcon size="lg" />
|
};
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText>Edit</ListItemText>
|
const onConfirmationDialogCancel = () => {
|
||||||
</MenuItem>
|
dispatch({
|
||||||
<MenuItem
|
type: 'SET_DELETE',
|
||||||
onClick={() => onOpenClosePilotForm(FormMode.VIEW, pilotId)}
|
payload: { isConfirmDialogOpen: false, selectedPilotId: undefined }
|
||||||
>
|
});
|
||||||
<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 columns: ColumnDef<Pilot>[] = [
|
const columns: ColumnDef<Pilot>[] = [
|
||||||
@@ -141,7 +129,13 @@ const Pilots: React.FC<unknown> = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Actions',
|
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 {
|
try {
|
||||||
const pilots = await getPilots();
|
const pilots = await getPilots();
|
||||||
|
|
||||||
setPilots(pilots);
|
dispatch({ type: 'SET_PILOTS', payload: pilots })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadPilots();
|
if (isAuthenticated && !state.isFormOpen) {
|
||||||
}, []);
|
loadPilots();
|
||||||
|
}
|
||||||
|
}, [isAuthenticated, state.isFormOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ margin: '20px' }}>
|
<Box sx={{ margin: '20px' }}>
|
||||||
@@ -166,25 +162,39 @@ const Pilots: React.FC<unknown> = () => {
|
|||||||
<Typography variant="h4">Pilots</Typography>
|
<Typography variant="h4">Pilots</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid display="flex" justifyContent="right" size={1}>
|
<Grid display="flex" justifyContent="right" size={1}>
|
||||||
<Button
|
{isAuthenticated &&
|
||||||
onClick={() => onOpenClosePilotForm(FormMode.ADD)}
|
<Button
|
||||||
startIcon={<PlusIcon />}
|
onClick={() => onOpenClosePilotForm(FormMode.ADD)}
|
||||||
variant="contained"
|
startIcon={<PlusIcon />}
|
||||||
data-testid="pilot-add-button"
|
variant="contained"
|
||||||
>
|
data-testid="pilot-add-button"
|
||||||
Add Pilot
|
>
|
||||||
</Button>
|
Add Pilot
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={12}>
|
<Grid size={12}>
|
||||||
{pilots.length > 0 && <Table columns={columns} data={pilots} />}
|
{state.pilots.length > 0 && <Table columns={columns} data={state.pilots} />}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<PilotForm
|
{state.isFormOpen && (
|
||||||
isDrawerOpen={isDrawerOpen}
|
<PilotForm
|
||||||
mode={pilotFormMode}
|
isDrawerOpen={state.isFormOpen}
|
||||||
onOpenClose={(mode) => onOpenClosePilotForm(mode)}
|
mode={state.formMode}
|
||||||
pilotId={selectedPilotId}
|
onOpenClose={(mode) => onOpenClosePilotForm(mode)}
|
||||||
/>
|
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>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
13
app/src/components/pilots/PilotsState.interface.ts
Normal file
13
app/src/components/pilots/PilotsState.interface.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { FormMode } from "../../enums/formMode";
|
||||||
|
import { Pilot } from "./Pilot.interface";
|
||||||
|
|
||||||
|
export interface PilotsState {
|
||||||
|
pilots: Pilot[];
|
||||||
|
error: string | undefined;
|
||||||
|
isConfirmDialogLoading: boolean;
|
||||||
|
isConfirmDialogOpen: boolean;
|
||||||
|
isFormOpen: boolean;
|
||||||
|
isLoading: boolean;
|
||||||
|
formMode: FormMode;
|
||||||
|
selectedPilotId: string | undefined;
|
||||||
|
}
|
||||||
92
app/src/components/pilots/reducer.ts
Normal file
92
app/src/components/pilots/reducer.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import { FormMode } from "../../enums/formMode";
|
||||||
|
import { Pilot } from "./Pilot.interface";
|
||||||
|
import { PilotsState } from './PilotsState.interface'
|
||||||
|
|
||||||
|
type Action =
|
||||||
|
| {
|
||||||
|
type: 'SET_DELETE';
|
||||||
|
payload: {
|
||||||
|
isConfirmDialogOpen: boolean;
|
||||||
|
selectedPilotId: string | undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| { type: 'SET_PILOTS'; payload: Pilot[] }
|
||||||
|
| { type: 'SET_ERROR'; payload: string | 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_ENTRY_FORM';
|
||||||
|
payload: {
|
||||||
|
formMode: FormMode;
|
||||||
|
selectedPilotId: string | undefined;
|
||||||
|
isFormOpen: boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initialState: PilotsState = {
|
||||||
|
pilots: [],
|
||||||
|
error: undefined,
|
||||||
|
formMode: FormMode.CANCEL,
|
||||||
|
isConfirmDialogLoading: false,
|
||||||
|
isConfirmDialogOpen: false,
|
||||||
|
isFormOpen: false,
|
||||||
|
isLoading: false,
|
||||||
|
selectedPilotId: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export const reducer = (
|
||||||
|
state: PilotsState,
|
||||||
|
action: Action
|
||||||
|
): PilotsState => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'SET_DELETE': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isConfirmDialogOpen: action.payload.isConfirmDialogOpen,
|
||||||
|
selectedPilotId: action.payload.selectedPilotId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'SET_PILOTS': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
pilots: action.payload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'SET_ERROR': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
error: action.payload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'SET_FORM_MODE': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
formMode: action.payload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'SET_IS_CONFIRMATION_DIALOG_LOADING': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isConfirmDialogLoading: action.payload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'SET_IS_LOADING': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isLoading: action.payload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'SET_OPEN_CLOSE_ENTRY_FORM': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
formMode: action.payload.formMode,
|
||||||
|
isFormOpen: action.payload.isFormOpen,
|
||||||
|
selectedPilotId: action.payload.selectedPilotId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user