From 01127e6f186344c9b83a9e2e97c6a306ab7c0045 Mon Sep 17 00:00:00 2001 From: Noah Spannbauer Date: Tue, 4 Feb 2025 18:32:07 -0600 Subject: [PATCH 1/2] adding pilot delete --- api/src/pilot/pilot.module.ts | 16 ++- api/src/pilot/pilot.service.ts | 20 ++- app/src/components/logbook/Logbook.tsx | 18 +-- app/src/components/pilots/Pilots.tsx | 192 +++++++++++++------------ 4 files changed, 143 insertions(+), 103 deletions(-) diff --git a/api/src/pilot/pilot.module.ts b/api/src/pilot/pilot.module.ts index 432f53c..78608b3 100644 --- a/api/src/pilot/pilot.module.ts +++ b/api/src/pilot/pilot.module.ts @@ -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('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] diff --git a/api/src/pilot/pilot.service.ts b/api/src/pilot/pilot.service.ts index 2daccf9..3cc321e 100644 --- a/api/src/pilot/pilot.service.ts +++ b/api/src/pilot/pilot.service.ts @@ -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 + @InjectRepository(Pilot) private readonly pilotRepository: Repository, + @InjectRepository(Log) private readonly logRepository: Repository ) {} async find(partitionKey: string, rowKey: string): Promise { @@ -35,6 +37,18 @@ export class PilotService { } async delete(partitionKey: string, rowKey: string): Promise { + 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 } } diff --git a/app/src/components/logbook/Logbook.tsx b/app/src/components/logbook/Logbook.tsx index 22869d6..5a18457 100644 --- a/app/src/components/logbook/Logbook.tsx +++ b/app/src/components/logbook/Logbook.tsx @@ -375,14 +375,16 @@ const Logbook: React.FC = () => { Logbook - + {isAuthenticated && + + } {!state.isLoading && state.error && ( diff --git a/app/src/components/pilots/Pilots.tsx b/app/src/components/pilots/Pilots.tsx index 79af763..73635e6 100644 --- a/app/src/components/pilots/Pilots.tsx +++ b/app/src/components/pilots/Pilots.tsx @@ -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 = () => { + 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.CANCEL); - const [selectedPilotId, setSelectedPilotId] = useState(); - const [pilots, setPilots] = useState([]); const getPilots = async (): Promise => { try { @@ -62,76 +56,70 @@ const Pilots: React.FC = () => { 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 ActionMenu = ({ pilotId }: ActionMenuProps) => { - const [anchorElAction, setAnchorElAction] = useState( - null - ); + const onConfirmationDialogConfirm = async () => { + try { + dispatch({ type: 'SET_IS_CONFIRMATION_DIALOG_LOADING', payload: true }); - const onOpenActionMenu = (event: React.MouseEvent) => { - console.log(event); - setAnchorElAction(event.currentTarget); - }; + const token = await getAccessToken(); + const config = isAuthenticated + ? { headers: { Authorization: `${token}` } } + : {}; - const onCloseActionMenu = () => { - setAnchorElAction(null); - }; + await httpClient.delete(`api/pilots/pilot/${state.selectedPilotId}`, config); - return ( -
- - - - - onOpenClosePilotForm(FormMode.EDIT, pilotId)} - > - - - - Edit - - onOpenClosePilotForm(FormMode.VIEW, pilotId)} - > - - - - View - -
- - - - - Delete - -
-
- ); + 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 onConfirmationDialogCancel = () => { + dispatch({ + type: 'SET_DELETE', + payload: { isConfirmDialogOpen: false, selectedPilotId: undefined } + }); }; const columns: ColumnDef[] = [ @@ -141,7 +129,13 @@ const Pilots: React.FC = () => { }, { header: 'Actions', - cell: (info) => + cell: (info) => ( + + ) } ]; @@ -150,14 +144,16 @@ const Pilots: React.FC = () => { try { const pilots = await getPilots(); - setPilots(pilots); + dispatch({ type: 'SET_PILOTS', payload: pilots }) } catch (error) { console.log(error); } }; - loadPilots(); - }, []); + if (isAuthenticated && !state.isFormOpen) { + loadPilots(); + } + }, [isAuthenticated, state.isFormOpen]); return ( @@ -166,25 +162,39 @@ const Pilots: React.FC = () => { Pilots
- + {isAuthenticated && + + } - {pilots.length > 0 && } + {state.pilots.length > 0 &&
} - onOpenClosePilotForm(mode)} - pilotId={selectedPilotId} - /> + {state.isFormOpen && ( + onOpenClosePilotForm(mode)} + pilotId={state.selectedPilotId} + /> + )} + {state.isConfirmDialogOpen && ( + + )} ); }; -- 2.49.1 From cf3a912d344e79d9132f14177bd14c7130f694f5 Mon Sep 17 00:00:00 2001 From: Noah Spannbauer Date: Tue, 4 Feb 2025 18:36:01 -0600 Subject: [PATCH 2/2] adding pilot delete --- app/src/components/pilots/Pilot.interface.ts | 6 ++ .../pilots/PilotsState.interface.ts | 13 +++ app/src/components/pilots/reducer.ts | 92 +++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 app/src/components/pilots/Pilot.interface.ts create mode 100644 app/src/components/pilots/PilotsState.interface.ts create mode 100644 app/src/components/pilots/reducer.ts diff --git a/app/src/components/pilots/Pilot.interface.ts b/app/src/components/pilots/Pilot.interface.ts new file mode 100644 index 0000000..965ed48 --- /dev/null +++ b/app/src/components/pilots/Pilot.interface.ts @@ -0,0 +1,6 @@ +export interface Pilot { + partitionKey: string; + rowKey: string; + id: string; + name: string; +}; \ No newline at end of file diff --git a/app/src/components/pilots/PilotsState.interface.ts b/app/src/components/pilots/PilotsState.interface.ts new file mode 100644 index 0000000..21b35bd --- /dev/null +++ b/app/src/components/pilots/PilotsState.interface.ts @@ -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; +} \ No newline at end of file diff --git a/app/src/components/pilots/reducer.ts b/app/src/components/pilots/reducer.ts new file mode 100644 index 0000000..8d2ab2c --- /dev/null +++ b/app/src/components/pilots/reducer.ts @@ -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; + } + } +} -- 2.49.1