From 014aef31aa666a00a49a4bc26ff544124637ec87 Mon Sep 17 00:00:00 2001 From: noahspannbauer Date: Mon, 18 Nov 2024 13:49:45 +0000 Subject: [PATCH] logbook view entry (#28) --- api/src/logbook/logbook.controller.ts | 27 ++++- api/src/logbook/logbook.entity.ts | 1 - api/src/logbook/logbook.service.ts | 38 +++++- app/src/actionMenu/ActionMenu.tsx | 68 +++++++++++ app/src/actionMenu/IActionMenuProps.tsx | 6 + app/src/components/logbook/Logbook.tsx | 25 +++- .../logbookEntryForm/LogbookEntryForm.tsx | 114 ++++++++++++------ app/src/components/pilotForm/PilotForm.tsx | 58 +++------ 8 files changed, 249 insertions(+), 88 deletions(-) create mode 100644 app/src/actionMenu/ActionMenu.tsx create mode 100644 app/src/actionMenu/IActionMenuProps.tsx diff --git a/api/src/logbook/logbook.controller.ts b/api/src/logbook/logbook.controller.ts index afe8c36..ffb44ab 100644 --- a/api/src/logbook/logbook.controller.ts +++ b/api/src/logbook/logbook.controller.ts @@ -1,4 +1,11 @@ -import { Body, Controller, Get, HttpException, Post } from '@nestjs/common'; +import { + Body, + Controller, + Get, + HttpException, + Param, + Post +} from '@nestjs/common'; import { LogbookService } from './logbook.service'; import { LogbookDto } from './logbook.dto'; import { CustomError } from '../customError/CustomError'; @@ -9,6 +16,24 @@ import { LogbookEntity } from './logbook.entity'; export class LogbookController { constructor(private readonly logbookService: LogbookService) {} + @Get(':entryId') + async find(@Param() params: any): Promise { + console.log(params); + try { + const entry: LogbookEntity = await this.logbookService.find( + params.entryId + ); + + return entry; + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode, { + cause: customError.name + }); + } + } + @Get() async findAll(): Promise { try { diff --git a/api/src/logbook/logbook.entity.ts b/api/src/logbook/logbook.entity.ts index 29b7965..479e423 100644 --- a/api/src/logbook/logbook.entity.ts +++ b/api/src/logbook/logbook.entity.ts @@ -1,7 +1,6 @@ export class LogbookEntity { partitionKey: string; rowKey: string; - id: string; pilotId: string; pilotName: string; date: string; diff --git a/api/src/logbook/logbook.service.ts b/api/src/logbook/logbook.service.ts index a4ef388..2ebc587 100644 --- a/api/src/logbook/logbook.service.ts +++ b/api/src/logbook/logbook.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { TableClient, TableService } from '@noahspan/noahspan-modules'; import { LogbookDto } from './logbook.dto'; import { LogbookEntity } from './logbook.entity'; -import { odata, RestError, TableInsertEntityHeaders } from '@azure/data-tables'; +import { RestError, TableInsertEntityHeaders } from '@azure/data-tables'; import { CustomError } from '../customError/CustomError'; import { v4 as uuidv4 } from 'uuid'; @@ -10,20 +10,20 @@ import { v4 as uuidv4 } from 'uuid'; export class LogbookService { constructor(private readonly tableService: TableService) {} - async findAll(): Promise { + async getLogbookEntries(filter: string): Promise { try { const client: TableClient = await this.tableService.getTableClient('Logbook'); const entities = await client.listEntities({ - queryOptions: { filter: odata`PartitionKey eq 'entry'` } + queryOptions: { filter: filter } }); + console.log(entities); const logbookEntries: LogbookEntity[] = []; for await (const entity of entities) { const logbookEntry = { partitionKey: entity.partitionKey.toString(), rowKey: entity.rowKey.toString(), - id: entity.rowKey.toString(), pilotId: entity.pilotId.toString(), pilotName: entity.pilotName.toString(), date: entity.date.toString(), @@ -89,6 +89,34 @@ export class LogbookService { } } + async find(entryId: string): Promise { + try { + const filter: string = `PartitionKey eq 'entry' and RowKey eq '${entryId}'`; + const entries: LogbookEntity[] = await this.getLogbookEntries(filter); + + return entries[0]; + } catch (error) { + throw error; + } + } + + async findAll(): Promise { + try { + const filter: string = `PartitionKey eq 'entry'`; + const entries: LogbookEntity[] = await this.getLogbookEntries(filter); + + return entries; + } catch (error) { + const restError: RestError = error as RestError; + + throw new CustomError( + restError.details['odataError']['message']['value'], + restError.details['odataError']['code'], + restError.statusCode + ); + } + } + async create(logbookData: LogbookDto): Promise { const client: TableClient = await this.tableService.getTableClient('Logbook'); @@ -96,7 +124,7 @@ export class LogbookService { Object.assign(logbook, logbookData); logbook.partitionKey = 'entry'; - logbook.rowKey = `${logbook.pilotId}:${uuidv4()}`; + logbook.rowKey = uuidv4(); try { return await client.createEntity(logbook); diff --git a/app/src/actionMenu/ActionMenu.tsx b/app/src/actionMenu/ActionMenu.tsx new file mode 100644 index 0000000..a686d38 --- /dev/null +++ b/app/src/actionMenu/ActionMenu.tsx @@ -0,0 +1,68 @@ +import { useEffect, useState } from 'react'; +import { IActionMenuProps } from './IActionMenuProps'; +import { + EllipsisVerticalIcon, + EyeIcon, + IconButton, + ListItemIcon, + ListItemText, + Menu, + MenuItem, + PenIcon, + TrashIcon +} from '@noahspan/noahspan-components'; +import { FormMode } from '../enums/formMode'; + +const ActionMenu = ({ id, onOpenCloseForm }: IActionMenuProps) => { + const [anchorElAction, setAnchorElAction] = useState( + null + ); + + const onOpenActionMenu = (event: React.MouseEvent) => { + setAnchorElAction(event.currentTarget); + }; + + const onCloseActionMenu = () => { + setAnchorElAction(null); + }; + + useEffect(() => { + console.log(id); + }, [id]); + + return ( +
+ + + + + onOpenCloseForm(FormMode.EDIT, id)}> + + + + Edit + + onOpenCloseForm(FormMode.VIEW, id)}> + + + + View + +
+ + + + + Delete + +
+
+ ); +}; + +export default ActionMenu; diff --git a/app/src/actionMenu/IActionMenuProps.tsx b/app/src/actionMenu/IActionMenuProps.tsx new file mode 100644 index 0000000..11af323 --- /dev/null +++ b/app/src/actionMenu/IActionMenuProps.tsx @@ -0,0 +1,6 @@ +import { FormMode } from '../enums/formMode'; + +export interface IActionMenuProps { + id: string; + onOpenCloseForm: (formMode: FormMode, id: string) => void; +} diff --git a/app/src/components/logbook/Logbook.tsx b/app/src/components/logbook/Logbook.tsx index 45647c7..f232e1b 100644 --- a/app/src/components/logbook/Logbook.tsx +++ b/app/src/components/logbook/Logbook.tsx @@ -14,6 +14,7 @@ import { AxiosInstance, AxiosResponse } from 'axios'; import { useAccessToken } from '../../hooks/accessToken/UseAcessToken'; import { useIsAuthenticated } from '@azure/msal-react'; import { FormMode } from '../../enums/formMode'; +import ActionMenu from '../../actionMenu/ActionMenu'; type LogbookEntry = { partitionKey: string; @@ -49,20 +50,20 @@ const Logbook: React.FC = () => { const { getAccessToken } = useAccessToken(); const [isDrawerOpen, setIsDrawerOpen] = useState(false); const [entryFormMode, setEntryFormMode] = useState(FormMode.CANCEL); - const [selectedPilotId, setSelectedPilotId] = useState(); + const [selectedEntryId, setSelectedEntryId] = useState(); const [entries, setEntries] = useState([]); - const onOpenCloseEntryForm = (mode: FormMode, pilotId?: string) => { + const onOpenCloseEntryForm = (mode: FormMode, entryId?: string) => { switch (mode) { case FormMode.ADD: case FormMode.EDIT: case FormMode.VIEW: setEntryFormMode(mode); - setSelectedPilotId(pilotId); + setSelectedEntryId(entryId); setIsDrawerOpen(true); break; case FormMode.CANCEL: setEntryFormMode(mode); - setSelectedPilotId(undefined); + setSelectedEntryId(undefined); setIsDrawerOpen(false); break; } @@ -292,6 +293,19 @@ const Logbook: React.FC = () => { { accessorKey: 'notes', header: 'Notes' + }, + { + header: 'Actions', + meta: { + align: 'center', + headerAlign: 'center' + }, + cell: (info) => ( + + ) } ]; @@ -307,7 +321,6 @@ const Logbook: React.FC = () => { config ); - console.log(response); setEntries(response.data); } catch (error) { console.log(error); @@ -340,7 +353,7 @@ const Logbook: React.FC = () => { onOpenCloseEntryForm(mode)} diff --git a/app/src/components/logbookEntryForm/LogbookEntryForm.tsx b/app/src/components/logbookEntryForm/LogbookEntryForm.tsx index 23f0c73..2c1fb28 100644 --- a/app/src/components/logbookEntryForm/LogbookEntryForm.tsx +++ b/app/src/components/logbookEntryForm/LogbookEntryForm.tsx @@ -37,6 +37,8 @@ const LogbookEntryForm: React.FC = ({ }) => { 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 }[] >([]); @@ -104,6 +106,39 @@ const LogbookEntryForm: React.FC = ({ } }; + useEffect(() => { + if (mode === FormMode.VIEW) { + setIsDisabled(true); + } + }, [mode]); + + useEffect(() => { + const getEntry = async () => { + try { + setIsLoading(true); + + const config = isAuthenticated + ? { headers: { Authorization: await getAccessToken() } } + : {}; + const response: AxiosResponse = await httpClient.get( + `api/logbook/${entryId}`, + config + ); + const entry = response.data; + + methods.reset(entry); + } catch (error) { + console.log(error); + } finally { + setIsLoading(false); + } + }; + + if (entryId) { + getEntry(); + } + }, [entryId]); + useEffect(() => { if (pilots) { const newPilotsOptions = pilots.map((pilot) => { @@ -149,6 +184,7 @@ const LogbookEntryForm: React.FC = ({ render={({ field: { onChange, value } }) => { return (