logbook view entry #28

Merged
noahspannbauer merged 1 commits from feature/26-logbook-view-entry into main 2024-11-18 08:49:45 -05:00
8 changed files with 249 additions and 88 deletions

View File

@@ -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<LogbookEntity> {
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<LogbookEntity[]> {
try {

View File

@@ -1,7 +1,6 @@
export class LogbookEntity {
partitionKey: string;
rowKey: string;
id: string;
pilotId: string;
pilotName: string;
date: string;

View File

@@ -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<LogbookEntity[]> {
async getLogbookEntries(filter: string): Promise<LogbookEntity[]> {
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<LogbookEntity> {
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<LogbookEntity[]> {
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<TableInsertEntityHeaders> {
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);

View File

@@ -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 | HTMLElement>(
null
);
const onOpenActionMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorElAction(event.currentTarget);
};
const onCloseActionMenu = () => {
setAnchorElAction(null);
};
useEffect(() => {
console.log(id);
}, [id]);
return (
<div>
<IconButton onClick={onOpenActionMenu}>
<EllipsisVerticalIcon size="sm" />
</IconButton>
<Menu
anchorEl={anchorElAction}
keepMounted
open={Boolean(anchorElAction)}
onClose={onCloseActionMenu}
>
<MenuItem onClick={() => onOpenCloseForm(FormMode.EDIT, id)}>
<ListItemIcon>
<PenIcon size="lg" />
</ListItemIcon>
<ListItemText>Edit</ListItemText>
</MenuItem>
<MenuItem onClick={() => onOpenCloseForm(FormMode.VIEW, id)}>
<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>
);
};
export default ActionMenu;

View File

@@ -0,0 +1,6 @@
import { FormMode } from '../enums/formMode';
export interface IActionMenuProps {
id: string;
onOpenCloseForm: (formMode: FormMode, id: string) => void;
}

View File

@@ -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<unknown> = () => {
const { getAccessToken } = useAccessToken();
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [entryFormMode, setEntryFormMode] = useState<FormMode>(FormMode.CANCEL);
const [selectedPilotId, setSelectedPilotId] = useState<string | undefined>();
const [selectedEntryId, setSelectedEntryId] = useState<string | undefined>();
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<unknown> = () => {
{
accessorKey: 'notes',
header: 'Notes'
},
{
header: 'Actions',
meta: {
align: 'center',
headerAlign: 'center'
},
cell: (info) => (
<ActionMenu
id={info.row.original.rowKey}
onOpenCloseForm={onOpenCloseEntryForm}
/>
)
}
];
@@ -307,7 +321,6 @@ const Logbook: React.FC<unknown> = () => {
config
);
console.log(response);
setEntries(response.data);
} catch (error) {
console.log(error);
@@ -340,7 +353,7 @@ const Logbook: React.FC<unknown> = () => {
</Grid>
</Grid>
<LogbookEntryForm
entryId={'1'}
entryId={selectedEntryId}
isDrawerOpen={isDrawerOpen}
mode={entryFormMode}
onOpenClose={(mode) => onOpenCloseEntryForm(mode)}

View File

@@ -37,6 +37,8 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
}) => {
const httpClient: AxiosInstance = useHttpClient();
const [isLoading, setIsLoading] = useState<boolean>(false);
const [selectedEntry, setSelectedEntry] = useState();
const [isDisabled, setIsDisabled] = useState<boolean>(false);
const [pilotOptions, setPilotOptions] = useState<
{ label: string; value: string }[]
>([]);
@@ -104,6 +106,39 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
}
};
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<ILogbookEntryFormProps> = ({
render={({ field: { onChange, value } }) => {
return (
<Select
disabled={isDisabled}
fullWidth
onChange={(event) => {
const pilot = pilots?.find(
@@ -180,7 +216,11 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
name="date"
control={methods.control}
render={({ field: { onChange, value } }) => (
<DatePicker onChange={onChange} value={value} />
<DatePicker
disabled={isDisabled}
onChange={onChange}
value={value}
/>
)}
/>
</Grid>
@@ -193,7 +233,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={methods.formState.errors.address ? true : false}
fullWidth
helperText={
@@ -216,7 +256,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={methods.formState.errors.address ? true : false}
fullWidth
helperText={
@@ -239,7 +279,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={methods.formState.errors.address ? true : false}
fullWidth
helperText={
@@ -262,7 +302,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={methods.formState.errors.address ? true : false}
fullWidth
helperText={
@@ -285,7 +325,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={methods.formState.errors.address ? true : false}
fullWidth
helperText={
@@ -314,7 +354,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={methods.formState.errors.address ? true : false}
fullWidth
helperText={
@@ -343,7 +383,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={methods.formState.errors.address ? true : false}
fullWidth
helperText={
@@ -379,7 +419,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={
methods.formState.errors.address ? true : false
}
@@ -410,7 +450,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={
methods.formState.errors.address ? true : false
}
@@ -452,7 +492,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={
methods.formState.errors.address ? true : false
}
@@ -483,7 +523,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={
methods.formState.errors.address ? true : false
}
@@ -516,7 +556,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={
methods.formState.errors.address ? true : false
}
@@ -547,7 +587,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={
methods.formState.errors.address ? true : false
}
@@ -578,7 +618,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={
methods.formState.errors.address ? true : false
}
@@ -622,7 +662,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={
methods.formState.errors.address ? true : false
}
@@ -655,7 +695,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={
methods.formState.errors.address ? true : false
}
@@ -686,7 +726,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={
methods.formState.errors.address ? true : false
}
@@ -717,7 +757,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={
methods.formState.errors.address ? true : false
}
@@ -748,7 +788,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={
methods.formState.errors.address ? true : false
}
@@ -779,7 +819,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={
methods.formState.errors.address ? true : false
}
@@ -814,7 +854,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
control={methods.control}
render={({ field: { onChange, value } }) => (
<TextField
// disabled={isDisabled}
disabled={isDisabled}
error={methods.formState.errors.address ? true : false}
fullWidth
helperText={
@@ -832,25 +872,29 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
</Grid>
<Grid display="flex" gap={2} justifyContent="right" size={12}>
<Button
// disabled={isDisabled}
disabled={
isDisabled && mode.toString() !== FormMode.VIEW
? isDisabled
: false
}
startIcon={<XmarkIcon />}
variant="outlined"
onClick={onCancel}
data-testid="pilot-cancel-button"
size="small"
>
Cancel
</Button>
<Button
// disabled={isDisabled}
startIcon={<SaveIcon />}
size="small"
type="submit"
variant="contained"
data-testid="pilot-save-button"
>
Save
{mode.toString() !== FormMode.VIEW ? 'Cancel' : 'Close'}
</Button>
{mode.toString() !== FormMode.VIEW && (
<Button
disabled={isDisabled}
startIcon={<SaveIcon />}
size="small"
type="submit"
variant="contained"
>
Save
</Button>
)}
</Grid>
</Grid>
</form>

View File

@@ -360,25 +360,31 @@ const PilotForm: React.FC<IPilotFormProps> = ({
</Grid>
<Grid display="flex" gap={2} justifyContent="right" size={12}>
<Button
disabled={isDisabled}
disabled={
isDisabled && mode.toString() !== FormMode.VIEW
? isDisabled
: false
}
startIcon={<XmarkIcon />}
variant="outlined"
onClick={onCancel}
data-testid="pilot-cancel-button"
size="small"
>
Cancel
</Button>
<Button
disabled={isDisabled}
startIcon={<SaveIcon />}
size="small"
type="submit"
variant="contained"
data-testid="pilot-save-button"
>
Save
{mode.toString() !== FormMode.VIEW ? 'Cancel' : 'Close'}
</Button>
{mode.toString() !== FormMode.VIEW && (
<Button
disabled={isDisabled}
startIcon={<SaveIcon />}
size="small"
type="submit"
variant="contained"
data-testid="pilot-save-button"
>
Save
</Button>
)}
</Grid>
</Grid>
{/* {pilotId && (
@@ -486,34 +492,6 @@ const PilotForm: React.FC<IPilotFormProps> = ({
<PilotFormEndorsements endorsements={[]} />
</>
)} */}
{/* </div>
{mode !== FormMode.VIEW && (
<div className="flex gap-2 justify-end justify-self-center pt-4">
<div>
<Button
disabled={isDisabled}
startIcon={<XmarkIcon />}
variant='contained'
onClick={onCancel}
data-testid="pilot-cancel-button"
>
Cancel
</Button>
</div>
<div>
<Button
disabled={isDisabled}
startIcon={<SaveIcon />}
size='medium'
type="submit"
variant='contained'
data-testid="pilot-save-button"
>
Save
</Button>
</div>
</div>
)} */}
</form>
</FormProvider>
</Drawer>