From d8180bb7ee3fdc61d3a7a615c78509a3ba54ba4f Mon Sep 17 00:00:00 2001 From: noahspannbauer Date: Tue, 19 Nov 2024 02:30:46 +0000 Subject: [PATCH] adding logbook entry edit (#29) --- api/src/logbook/logbook.controller.ts | 22 ++- api/src/logbook/logbook.service.ts | 36 +++- app/src/actionMenu/ActionMenu.tsx | 6 +- .../ILogbookEntryFormState.ts | 6 + .../logbookEntryForm/LogbookEntryForm.tsx | 155 ++++++++++-------- .../components/logbookEntryForm/reducer.tsx | 46 ++++++ 6 files changed, 186 insertions(+), 85 deletions(-) create mode 100644 app/src/components/logbookEntryForm/ILogbookEntryFormState.ts create mode 100644 app/src/components/logbookEntryForm/reducer.tsx diff --git a/api/src/logbook/logbook.controller.ts b/api/src/logbook/logbook.controller.ts index ffb44ab..5d9f3a9 100644 --- a/api/src/logbook/logbook.controller.ts +++ b/api/src/logbook/logbook.controller.ts @@ -4,7 +4,8 @@ import { Get, HttpException, Param, - Post + Post, + Put } from '@nestjs/common'; import { LogbookService } from './logbook.service'; import { LogbookDto } from './logbook.dto'; @@ -18,7 +19,6 @@ export class LogbookController { @Get(':entryId') async find(@Param() params: any): Promise { - console.log(params); try { const entry: LogbookEntity = await this.logbookService.find( params.entryId @@ -43,7 +43,7 @@ export class LogbookController { return logbookEntries; } catch (error) { const customError = error as CustomError; - console.log(error); + throw new HttpException(customError.message, customError.statusCode, { cause: customError.name }); @@ -63,4 +63,20 @@ export class LogbookController { }); } } + + @Put(':entryId') + async update( + @Param() params: any, + @Body() logbookData: LogbookDto + ): Promise { + try { + return await this.logbookService.update(logbookData); + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode, { + cause: customError.name + }); + } + } } diff --git a/api/src/logbook/logbook.service.ts b/api/src/logbook/logbook.service.ts index 2ebc587..a0a203e 100644 --- a/api/src/logbook/logbook.service.ts +++ b/api/src/logbook/logbook.service.ts @@ -8,16 +8,18 @@ import { v4 as uuidv4 } from 'uuid'; @Injectable() export class LogbookService { + private readonly tableName = 'Logbook'; + constructor(private readonly tableService: TableService) {} async getLogbookEntries(filter: string): Promise { try { - const client: TableClient = - await this.tableService.getTableClient('Logbook'); + const client: TableClient = await this.tableService.getTableClient( + this.tableName + ); const entities = await client.listEntities({ queryOptions: { filter: filter } }); - console.log(entities); const logbookEntries: LogbookEntity[] = []; for await (const entity of entities) { @@ -73,7 +75,7 @@ export class LogbookService { : null, notes: entity.notes.toString() }; - console.log(logbookEntry); + logbookEntries.push(logbookEntry); } @@ -118,8 +120,9 @@ export class LogbookService { } async create(logbookData: LogbookDto): Promise { - const client: TableClient = - await this.tableService.getTableClient('Logbook'); + const client: TableClient = await this.tableService.getTableClient( + this.tableName + ); const logbook: LogbookEntity = new LogbookEntity(); Object.assign(logbook, logbookData); @@ -138,4 +141,25 @@ export class LogbookService { ); } } + + async update(logbookData: LogbookDto): Promise { + const client: TableClient = await this.tableService.getTableClient( + this.tableName + ); + const logbook: LogbookEntity = new LogbookEntity(); + + Object.assign(logbook, logbookData); + + try { + await client.upsertEntity(logbook, 'Replace'); + } catch (error) { + const restError: RestError = error as RestError; + + throw new CustomError( + restError.details['odataError']['message']['value'], + restError.details['odataError']['code'], + restError.statusCode + ); + } + } } diff --git a/app/src/actionMenu/ActionMenu.tsx b/app/src/actionMenu/ActionMenu.tsx index a686d38..2c89f1c 100644 --- a/app/src/actionMenu/ActionMenu.tsx +++ b/app/src/actionMenu/ActionMenu.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { IActionMenuProps } from './IActionMenuProps'; import { EllipsisVerticalIcon, @@ -26,10 +26,6 @@ const ActionMenu = ({ id, onOpenCloseForm }: IActionMenuProps) => { setAnchorElAction(null); }; - useEffect(() => { - console.log(id); - }, [id]); - return (
diff --git a/app/src/components/logbookEntryForm/ILogbookEntryFormState.ts b/app/src/components/logbookEntryForm/ILogbookEntryFormState.ts new file mode 100644 index 0000000..3161d06 --- /dev/null +++ b/app/src/components/logbookEntryForm/ILogbookEntryFormState.ts @@ -0,0 +1,6 @@ +export interface ILogbookEntryFormState { + isDisabled: boolean; + isLoading: boolean; + pilotOptions: { label: string; value: string }[]; + selectedEntryPilotName: string; +} diff --git a/app/src/components/logbookEntryForm/LogbookEntryForm.tsx b/app/src/components/logbookEntryForm/LogbookEntryForm.tsx index 2c1fb28..029e979 100644 --- a/app/src/components/logbookEntryForm/LogbookEntryForm.tsx +++ b/app/src/components/logbookEntryForm/LogbookEntryForm.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useReducer, useState } from 'react'; import { Accordion, AccordionDetails, @@ -15,13 +15,9 @@ import { Typography, XmarkIcon } from '@noahspan/noahspan-components'; -import { - useForm, - Controller, - SubmitHandler, - FormProvider -} from 'react-hook-form'; +import { useForm, Controller, FormProvider } from 'react-hook-form'; import { ILogbookEntryFormProps } from './ILogbookEntryFormProps'; +import { initialState, reducer } from './reducer'; import axios, { AxiosInstance, AxiosResponse } from 'axios'; import { useHttpClient } from '../../hooks/httpClient/UseHttpClient'; import { useAccessToken } from '../../hooks/accessToken/UseAcessToken'; @@ -35,13 +31,8 @@ const LogbookEntryForm: React.FC = ({ mode, onOpenClose }) => { + const [state, dispatch] = useReducer(reducer, initialState); const httpClient: AxiosInstance = useHttpClient(); - const [isLoading, setIsLoading] = useState(false); - const [selectedEntry, setSelectedEntry] = useState(); - const [isDisabled, setIsDisabled] = useState(false); - const [pilotOptions, setPilotOptions] = useState< - { label: string; value: string }[] - >([]); const { getAccessToken } = useAccessToken(); const isAuthenticated = useIsAuthenticated(); const defaultValues = { @@ -77,45 +68,55 @@ const LogbookEntryForm: React.FC = ({ const onCancel = () => { methods.reset(defaultValues); + dispatch({ type: 'SET_IS_DISABLED', payload: false }); onOpenClose(FormMode.CANCEL); }; const onSubmit = async (data: unknown) => { try { - setIsLoading(true); + dispatch({ type: 'SET_IS_LOADING', payload: true }); const accessToken: string = await getAccessToken(); - await httpClient.post(`api/logbook`, data, { - headers: { - Authorization: accessToken - } - }); + if (!entryId) { + await httpClient.post(`api/logbook`, data, { + headers: { + Authorization: accessToken + } + }); + } else { + await httpClient.put(`api/logbook/${entryId}`, data, { + headers: { + Authorization: accessToken + } + }); + } methods.reset(defaultValues); + dispatch({ type: 'SET_IS_DISABLED', payload: false }); onOpenClose(FormMode.CANCEL); } catch (error) { if (axios.isAxiosError(error)) { const errResp = error.response; - console.log(errResp?.data.message); + console.log(errResp); } else { } } finally { - setIsLoading(false); + dispatch({ type: 'SET_IS_LOADING', payload: false }); } }; useEffect(() => { if (mode === FormMode.VIEW) { - setIsDisabled(true); + dispatch({ type: 'SET_IS_DISABLED', payload: true }); } }, [mode]); useEffect(() => { const getEntry = async () => { try { - setIsLoading(true); + dispatch({ type: 'SET_IS_LOADING', payload: true }); const config = isAuthenticated ? { headers: { Authorization: await getAccessToken() } } @@ -130,7 +131,7 @@ const LogbookEntryForm: React.FC = ({ } catch (error) { console.log(error); } finally { - setIsLoading(false); + dispatch({ type: 'SET_IS_LOADING', payload: false }); } }; @@ -148,7 +149,7 @@ const LogbookEntryForm: React.FC = ({ }; }); - setPilotOptions(newPilotsOptions); + dispatch({ type: 'SET_PILOT_OPTIONS', payload: newPilotsOptions }); } }, [pilots]); @@ -182,28 +183,40 @@ const LogbookEntryForm: React.FC = ({ name="pilotId" control={methods.control} render={({ field: { onChange, value } }) => { + useEffect(() => { + if (value && mode !== FormMode.ADD) { + const pilot = pilots?.find((pilot) => pilot.id === value); + + dispatch({ + type: 'SET_SELECTED_ENTRY_PILOT_NAME', + payload: pilot.name + }); + } + }, [value]); + return ( - 0 + ? state.pilotOptions + : [] + } + value={value} + /> + )} + {mode !== FormMode.ADD && ( + + )} + ); }} /> @@ -217,7 +230,7 @@ const LogbookEntryForm: React.FC = ({ control={methods.control} render={({ field: { onChange, value } }) => ( @@ -233,7 +246,7 @@ const LogbookEntryForm: React.FC = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ control={methods.control} render={({ field: { onChange, value } }) => ( = ({ {mode.toString() !== FormMode.VIEW && (