adding logbook entry delete #31
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
|
Delete,
|
||||||
Get,
|
Get,
|
||||||
HttpException,
|
HttpException,
|
||||||
Param,
|
Param,
|
||||||
@@ -79,4 +80,17 @@ export class LogbookController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Delete(':rowKey')
|
||||||
|
async delete(@Param() params: any): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.logbookService.delete(params.rowKey);
|
||||||
|
} catch (error) {
|
||||||
|
const customError = error as CustomError;
|
||||||
|
|
||||||
|
throw new HttpException(customError.message, customError.statusCode, {
|
||||||
|
cause: customError.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,19 +10,19 @@ export class LogbookEntity {
|
|||||||
routeTo: string;
|
routeTo: string;
|
||||||
durationOfFlight: number | null;
|
durationOfFlight: number | null;
|
||||||
singleEngineLand: number | null;
|
singleEngineLand: number | null;
|
||||||
simulatorAtd: number | null;
|
simulatorAtd?: number | null;
|
||||||
landingsDay: number | null;
|
landingsDay?: number | null;
|
||||||
landingsNight: number | null;
|
landingsNight?: number | null;
|
||||||
groundTrainingReceived: number;
|
groundTrainingReceived?: number;
|
||||||
flightTrainingReceived: number;
|
flightTrainingReceived?: number;
|
||||||
crossCountry: number | null;
|
crossCountry?: number | null;
|
||||||
night: number | null;
|
night?: number | null;
|
||||||
solo: number | null;
|
solo?: number | null;
|
||||||
pilotInCommand: number | null;
|
pilotInCommand?: number | null;
|
||||||
instrumentActual: number | null;
|
instrumentActual?: number | null;
|
||||||
instrumentSimulated: number | null;
|
instrumentSimulated?: number | null;
|
||||||
instrumentApproaches: number | null;
|
instrumentApproaches?: number | null;
|
||||||
instrumentHolds: number | null;
|
instrumentHolds?: number | null;
|
||||||
instrumentNavTrack: number | null;
|
instrumentNavTrack?: number | null;
|
||||||
notes: string;
|
notes?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export class LogbookService {
|
|||||||
instrumentNavTrack: entity.instrumentNavTrack
|
instrumentNavTrack: entity.instrumentNavTrack
|
||||||
? Number(entity.instrumentNavTrack)
|
? Number(entity.instrumentNavTrack)
|
||||||
: null,
|
: null,
|
||||||
notes: entity.notes.toString()
|
notes: entity.notes ? entity.notes.toString() : ''
|
||||||
};
|
};
|
||||||
|
|
||||||
logbookEntries.push(logbookEntry);
|
logbookEntries.push(logbookEntry);
|
||||||
@@ -143,6 +143,7 @@ export class LogbookService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async update(logbookData: LogbookDto): Promise<void> {
|
async update(logbookData: LogbookDto): Promise<void> {
|
||||||
|
try {
|
||||||
const client: TableClient = await this.tableService.getTableClient(
|
const client: TableClient = await this.tableService.getTableClient(
|
||||||
this.tableName
|
this.tableName
|
||||||
);
|
);
|
||||||
@@ -150,7 +151,6 @@ export class LogbookService {
|
|||||||
|
|
||||||
Object.assign(logbook, logbookData);
|
Object.assign(logbook, logbookData);
|
||||||
|
|
||||||
try {
|
|
||||||
await client.upsertEntity(logbook, 'Replace');
|
await client.upsertEntity(logbook, 'Replace');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const restError: RestError = error as RestError;
|
const restError: RestError = error as RestError;
|
||||||
@@ -162,4 +162,22 @@ export class LogbookService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async delete(rowKey: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const client: TableClient = await this.tableService.getTableClient(
|
||||||
|
this.tableName
|
||||||
|
);
|
||||||
|
|
||||||
|
await client.deleteEntity('entry', rowKey);
|
||||||
|
} catch (error) {
|
||||||
|
const restError: RestError = error as RestError;
|
||||||
|
|
||||||
|
throw new CustomError(
|
||||||
|
restError.details['odataError']['message']['value'],
|
||||||
|
restError.details['odataError']['code'],
|
||||||
|
restError.statusCode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ import {
|
|||||||
PenIcon,
|
PenIcon,
|
||||||
TrashIcon
|
TrashIcon
|
||||||
} from '@noahspan/noahspan-components';
|
} from '@noahspan/noahspan-components';
|
||||||
import { FormMode } from '../enums/formMode';
|
import { FormMode } from '../../enums/formMode';
|
||||||
|
|
||||||
const ActionMenu = ({ id, onOpenCloseForm }: IActionMenuProps) => {
|
const ActionMenu = ({ id, onDelete, onOpenCloseForm }: IActionMenuProps) => {
|
||||||
const [anchorElAction, setAnchorElAction] = useState<null | HTMLElement>(
|
const [anchorElAction, setAnchorElAction] = useState<null | HTMLElement>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
@@ -50,7 +50,7 @@ const ActionMenu = ({ id, onOpenCloseForm }: IActionMenuProps) => {
|
|||||||
<ListItemText>View</ListItemText>
|
<ListItemText>View</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<hr className="my-3" />
|
<hr className="my-3" />
|
||||||
<MenuItem>
|
<MenuItem onClick={() => onDelete(id)}>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<TrashIcon size="lg" />
|
<TrashIcon size="lg" />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { FormMode } from '../enums/formMode';
|
import { FormMode } from '../../enums/formMode';
|
||||||
|
|
||||||
export interface IActionMenuProps {
|
export interface IActionMenuProps {
|
||||||
id: string;
|
id: string;
|
||||||
|
onDelete: (entryId: string) => void;
|
||||||
onOpenCloseForm: (formMode: FormMode, id: string) => void;
|
onOpenCloseForm: (formMode: FormMode, id: string) => void;
|
||||||
}
|
}
|
||||||
52
app/src/components/confirmationDialog/ConfirmationDialog.tsx
Normal file
52
app/src/components/confirmationDialog/ConfirmationDialog.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
CircleCheckIcon,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogContentText,
|
||||||
|
DialogTitle,
|
||||||
|
Spinner,
|
||||||
|
XmarkIcon
|
||||||
|
} from '@noahspan/noahspan-components';
|
||||||
|
import { IDialogConfirmationProps } from './IConfirmationDialogProps';
|
||||||
|
|
||||||
|
const ConfirmationDialog = ({
|
||||||
|
contentText,
|
||||||
|
isLoading,
|
||||||
|
isOpen,
|
||||||
|
onCancel,
|
||||||
|
onConfirm,
|
||||||
|
title
|
||||||
|
}: IDialogConfirmationProps) => {
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
onClose={onCancel}
|
||||||
|
open={isOpen}
|
||||||
|
sx={{
|
||||||
|
'& .MuiDialog-paper': { width: '2000px' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTitle>{title}</DialogTitle>
|
||||||
|
<DialogContent sx={{ textAlign: 'center' }}>
|
||||||
|
{!isLoading && <DialogContentText>{contentText}</DialogContentText>}
|
||||||
|
{isLoading && <Spinner />}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={onCancel} variant="outlined" startIcon={<XmarkIcon />}>
|
||||||
|
No
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={onConfirm}
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<CircleCheckIcon />}
|
||||||
|
>
|
||||||
|
Yes
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConfirmationDialog;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export interface IDialogConfirmationProps {
|
||||||
|
contentText: string;
|
||||||
|
isLoading: boolean;
|
||||||
|
isOpen: boolean;
|
||||||
|
onCancel: () => void;
|
||||||
|
onConfirm: () => void;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
27
app/src/components/logbook/ILogbookEntry.ts
Normal file
27
app/src/components/logbook/ILogbookEntry.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
export interface ILogbookEntry {
|
||||||
|
partitionKey: string;
|
||||||
|
rowKey: string;
|
||||||
|
id: string;
|
||||||
|
pilotId: string;
|
||||||
|
date: string;
|
||||||
|
aircraftMakeModel: string;
|
||||||
|
aircraftIdentity: string;
|
||||||
|
routeFrom: string;
|
||||||
|
routeTo: string;
|
||||||
|
durationOfFlight: number | null;
|
||||||
|
singleEngineLand: number | null;
|
||||||
|
simulatorAtd: number | null;
|
||||||
|
landingsDay: number | null;
|
||||||
|
landingsNight: number | null;
|
||||||
|
instrumentActual: number | null;
|
||||||
|
instrumentSimulated: number | null;
|
||||||
|
instrumentApproaches: number | null;
|
||||||
|
instrumentHolds: number | null;
|
||||||
|
instrumentNavTrack: number | null;
|
||||||
|
groundTrainingReceived: number;
|
||||||
|
flightTrainingReceived: number;
|
||||||
|
crossCountry: number | null;
|
||||||
|
night: number | null;
|
||||||
|
solo: number | null;
|
||||||
|
pilotInCommand: number | null;
|
||||||
|
}
|
||||||
13
app/src/components/logbook/ILogbookState.ts
Normal file
13
app/src/components/logbook/ILogbookState.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { FormMode } from '../../enums/formMode';
|
||||||
|
import { ILogbookEntry } from './ILogbookEntry';
|
||||||
|
|
||||||
|
export interface ILogbookState {
|
||||||
|
entries: ILogbookEntry[];
|
||||||
|
formMode: FormMode;
|
||||||
|
error: string | undefined;
|
||||||
|
isConfirmDialogLoading: boolean;
|
||||||
|
isConfirmDialogOpen: boolean;
|
||||||
|
isFormOpen: boolean;
|
||||||
|
isLoading: boolean;
|
||||||
|
selectedEntryId: string | undefined;
|
||||||
|
}
|
||||||
@@ -1,75 +1,130 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useReducer, useState } from 'react';
|
||||||
import LogbookEntryForm from '../logbookEntryForm/LogbookEntryForm';
|
import LogbookEntryForm from '../logbookEntryForm/LogbookEntryForm';
|
||||||
import {
|
import {
|
||||||
|
Alert,
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
ColumnDef,
|
ColumnDef,
|
||||||
Grid,
|
Grid,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
|
Spinner,
|
||||||
Table,
|
Table,
|
||||||
Typography
|
Typography
|
||||||
} from '@noahspan/noahspan-components';
|
} from '@noahspan/noahspan-components';
|
||||||
|
import { initialState, reducer } from './reducer';
|
||||||
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 ActionMenu from '../../actionMenu/ActionMenu';
|
import ActionMenu from '../actionMenu/ActionMenu';
|
||||||
|
import ConfirmationDialog from '../confirmationDialog/ConfirmationDialog';
|
||||||
type LogbookEntry = {
|
import { ILogbookEntry } from './ILogbookEntry';
|
||||||
partitionKey: string;
|
|
||||||
rowKey: string;
|
|
||||||
id: string;
|
|
||||||
pilotId: string;
|
|
||||||
date: string;
|
|
||||||
aircraftMakeModel: string;
|
|
||||||
aircraftIdentity: string;
|
|
||||||
routeFrom: string;
|
|
||||||
routeTo: string;
|
|
||||||
durationOfFlight: number | null;
|
|
||||||
singleEngineLand: number | null;
|
|
||||||
simulatorAtd: number | null;
|
|
||||||
landingsDay: number | null;
|
|
||||||
landingsNight: number | null;
|
|
||||||
instrumentActual: number | null;
|
|
||||||
instrumentSimulated: number | null;
|
|
||||||
instrumentApproaches: number | null;
|
|
||||||
instrumentHolds: number | null;
|
|
||||||
instrumentNavTrack: number | null;
|
|
||||||
groundTrainingReceived: number;
|
|
||||||
flightTrainingReceived: number;
|
|
||||||
crossCountry: number | null;
|
|
||||||
night: number | null;
|
|
||||||
solo: number | null;
|
|
||||||
pilotInCommand: number | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Logbook: React.FC<unknown> = () => {
|
const Logbook: 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 [entryFormMode, setEntryFormMode] = useState<FormMode>(FormMode.CANCEL);
|
const getLogbookEntries = async () => {
|
||||||
const [selectedEntryId, setSelectedEntryId] = useState<string | undefined>();
|
try {
|
||||||
const [entries, setEntries] = useState([]);
|
dispatch({ type: 'SET_IS_LOADING', payload: true });
|
||||||
|
|
||||||
|
const token = await getAccessToken();
|
||||||
|
const config = isAuthenticated
|
||||||
|
? { headers: { Authorization: `${token}` } }
|
||||||
|
: {};
|
||||||
|
const response: AxiosResponse = await httpClient.get(
|
||||||
|
`api/logbook`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch({ type: 'SET_ENTRIES', payload: response.data });
|
||||||
|
} 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_LOADING', payload: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onOpenCloseEntryForm = (mode: FormMode, entryId?: string) => {
|
const onOpenCloseEntryForm = (mode: FormMode, entryId?: string) => {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case FormMode.ADD:
|
case FormMode.ADD:
|
||||||
case FormMode.EDIT:
|
case FormMode.EDIT:
|
||||||
case FormMode.VIEW:
|
case FormMode.VIEW:
|
||||||
setEntryFormMode(mode);
|
dispatch({
|
||||||
setSelectedEntryId(entryId);
|
type: 'SET_OPEN_CLOSE_ENTRY_FORM',
|
||||||
setIsDrawerOpen(true);
|
payload: {
|
||||||
|
formMode: mode,
|
||||||
|
selectedEntryId: entryId,
|
||||||
|
isFormOpen: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case FormMode.CANCEL:
|
case FormMode.CANCEL:
|
||||||
setEntryFormMode(mode);
|
dispatch({
|
||||||
setSelectedEntryId(undefined);
|
type: 'SET_OPEN_CLOSE_ENTRY_FORM',
|
||||||
setIsDrawerOpen(false);
|
payload: {
|
||||||
|
formMode: mode,
|
||||||
|
selectedEntryId: undefined,
|
||||||
|
isFormOpen: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns: ColumnDef<LogbookEntry>[] = [
|
const onDeleteEntry = (entryId: string) => {
|
||||||
|
dispatch({
|
||||||
|
type: 'SET_DELETE',
|
||||||
|
payload: { isConfirmationDialogOpen: true, selectedEntryId: entryId }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onConfirmationDialogConfirm = async () => {
|
||||||
|
try {
|
||||||
|
dispatch({ type: 'SET_IS_CONFIRMATION_DIALOG_LOADING', payload: true });
|
||||||
|
|
||||||
|
const token = await getAccessToken();
|
||||||
|
const config = isAuthenticated
|
||||||
|
? { headers: { Authorization: `${token}` } }
|
||||||
|
: {};
|
||||||
|
|
||||||
|
await httpClient.delete(`api/logbook/${state.selectedEntryId}`, config);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: 'SET_DELETE',
|
||||||
|
payload: { isConfirmationDialogOpen: false, selectedEntryId: undefined }
|
||||||
|
});
|
||||||
|
await getLogbookEntries();
|
||||||
|
} 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: { isConfirmationDialogOpen: false, selectedEntryId: undefined }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns: ColumnDef<ILogbookEntry>[] = [
|
||||||
{
|
{
|
||||||
accessorKey: 'pilotName',
|
accessorKey: 'pilotName',
|
||||||
header: 'Pilot'
|
header: 'Pilot'
|
||||||
@@ -303,6 +358,7 @@ const Logbook: React.FC<unknown> = () => {
|
|||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<ActionMenu
|
<ActionMenu
|
||||||
id={info.row.original.rowKey}
|
id={info.row.original.rowKey}
|
||||||
|
onDelete={onDeleteEntry}
|
||||||
onOpenCloseForm={onOpenCloseEntryForm}
|
onOpenCloseForm={onOpenCloseEntryForm}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -310,27 +366,10 @@ const Logbook: React.FC<unknown> = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getLogbookEntries = async () => {
|
if (isAuthenticated && !state.isFormOpen) {
|
||||||
try {
|
|
||||||
const token = await getAccessToken();
|
|
||||||
const config = isAuthenticated
|
|
||||||
? { headers: { Authorization: `${token}` } }
|
|
||||||
: {};
|
|
||||||
const response: AxiosResponse = await httpClient.get(
|
|
||||||
`api/logbook`,
|
|
||||||
config
|
|
||||||
);
|
|
||||||
|
|
||||||
setEntries(response.data);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isAuthenticated && !isDrawerOpen) {
|
|
||||||
getLogbookEntries();
|
getLogbookEntries();
|
||||||
}
|
}
|
||||||
}, [isAuthenticated, isDrawerOpen]);
|
}, [isAuthenticated, state.isFormOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ margin: '20px' }}>
|
<Box sx={{ margin: '20px' }}>
|
||||||
@@ -348,16 +387,51 @@ const Logbook: React.FC<unknown> = () => {
|
|||||||
Add Entry
|
Add Entry
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={12}>
|
{!state.isLoading && state.error && (
|
||||||
{entries.length > 0 && <Table columns={columns} data={entries} />}
|
<Grid display="flex" justifyContent="center" size={12}>
|
||||||
|
<Alert
|
||||||
|
onClose={() =>
|
||||||
|
dispatch({ type: 'SET_ERROR', payload: undefined })
|
||||||
|
}
|
||||||
|
severity="error"
|
||||||
|
sx={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
{state.error}
|
||||||
|
</Alert>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
)}
|
||||||
|
{!state.isLoading && (
|
||||||
|
<Grid size={12}>
|
||||||
|
{state.entries.length > 0 && (
|
||||||
|
<Table columns={columns} data={state.entries} />
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
{state.isLoading && !state.error && (
|
||||||
|
<>
|
||||||
|
<Grid display="flex" justifyContent="center" size={12}>
|
||||||
|
<Spinner />
|
||||||
|
</Grid>
|
||||||
|
<Grid display="flex" justifyContent="center" size={12}>
|
||||||
|
Loading...
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
<LogbookEntryForm
|
<LogbookEntryForm
|
||||||
entryId={selectedEntryId}
|
entryId={state.selectedEntryId}
|
||||||
isDrawerOpen={isDrawerOpen}
|
isDrawerOpen={state.isFormOpen}
|
||||||
mode={entryFormMode}
|
mode={state.formMode}
|
||||||
onOpenClose={(mode) => onOpenCloseEntryForm(mode)}
|
onOpenClose={(mode) => onOpenCloseEntryForm(mode)}
|
||||||
/>
|
/>
|
||||||
|
<ConfirmationDialog
|
||||||
|
contentText="Are you sure you want to delete the logbook entry?"
|
||||||
|
isLoading={state.isConfirmDialogLoading}
|
||||||
|
isOpen={state.isConfirmDialogOpen}
|
||||||
|
onCancel={onConfirmationDialogCancel}
|
||||||
|
onConfirm={onConfirmationDialogConfirm}
|
||||||
|
title="Confirm Delete"
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
92
app/src/components/logbook/reducer.ts
Normal file
92
app/src/components/logbook/reducer.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import { FormMode } from '../../enums/formMode';
|
||||||
|
import { ILogbookEntry } from './ILogbookEntry';
|
||||||
|
import { ILogbookState } from './ILogbookState';
|
||||||
|
|
||||||
|
type Action =
|
||||||
|
| {
|
||||||
|
type: 'SET_DELETE';
|
||||||
|
payload: {
|
||||||
|
isConfirmationDialogOpen: boolean;
|
||||||
|
selectedEntryId: string | undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| { type: 'SET_ENTRIES'; payload: ILogbookEntry[] }
|
||||||
|
| { 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;
|
||||||
|
selectedEntryId: string | undefined;
|
||||||
|
isFormOpen: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initialState: ILogbookState = {
|
||||||
|
entries: [],
|
||||||
|
error: undefined,
|
||||||
|
formMode: FormMode.CANCEL,
|
||||||
|
isConfirmDialogLoading: false,
|
||||||
|
isConfirmDialogOpen: false,
|
||||||
|
isFormOpen: false,
|
||||||
|
isLoading: false,
|
||||||
|
selectedEntryId: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reducer = (
|
||||||
|
state: ILogbookState,
|
||||||
|
action: Action
|
||||||
|
): ILogbookState => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'SET_DELETE': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isConfirmDialogOpen: action.payload.isConfirmationDialogOpen,
|
||||||
|
selectedEntryId: action.payload.selectedEntryId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'SET_ENTRIES': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
entries: 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,
|
||||||
|
selectedEntryId: action.payload.selectedEntryId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export interface ILogbookEntryFormState {
|
export interface ILogbookEntryFormState {
|
||||||
|
error: string | undefined;
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
pilotOptions: { label: string; value: string }[];
|
pilotOptions: { label: string; value: string }[];
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
Accordion,
|
Accordion,
|
||||||
AccordionDetails,
|
AccordionDetails,
|
||||||
AccordionSummary,
|
AccordionSummary,
|
||||||
|
Alert,
|
||||||
Button,
|
Button,
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
DatePicker,
|
DatePicker,
|
||||||
@@ -18,7 +19,7 @@ import {
|
|||||||
import { useForm, Controller, FormProvider } from 'react-hook-form';
|
import { useForm, Controller, FormProvider } from 'react-hook-form';
|
||||||
import { ILogbookEntryFormProps } from './ILogbookEntryFormProps';
|
import { ILogbookEntryFormProps } from './ILogbookEntryFormProps';
|
||||||
import { initialState, reducer } from './reducer';
|
import { initialState, reducer } from './reducer';
|
||||||
import axios, { AxiosInstance, AxiosResponse } from 'axios';
|
import axios, { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
|
||||||
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
||||||
import { useAccessToken } from '../../hooks/accessToken/UseAcessToken';
|
import { useAccessToken } from '../../hooks/accessToken/UseAcessToken';
|
||||||
import { useIsAuthenticated } from '@azure/msal-react';
|
import { useIsAuthenticated } from '@azure/msal-react';
|
||||||
@@ -96,12 +97,9 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
|
|||||||
dispatch({ type: 'SET_IS_DISABLED', payload: false });
|
dispatch({ type: 'SET_IS_DISABLED', payload: false });
|
||||||
onOpenClose(FormMode.CANCEL);
|
onOpenClose(FormMode.CANCEL);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (axios.isAxiosError(error)) {
|
const axiosError = error as AxiosError;
|
||||||
const errResp = error.response;
|
|
||||||
|
|
||||||
console.log(errResp);
|
dispatch({ type: 'SET_ERROR', payload: axiosError.message });
|
||||||
} else {
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
dispatch({ type: 'SET_IS_LOADING', payload: false });
|
dispatch({ type: 'SET_IS_LOADING', payload: false });
|
||||||
}
|
}
|
||||||
@@ -129,13 +127,15 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
|
|||||||
|
|
||||||
methods.reset(entry);
|
methods.reset(entry);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
const axiosError = error as AxiosError;
|
||||||
|
|
||||||
|
dispatch({ type: 'SET_ERROR', payload: axiosError.message });
|
||||||
} finally {
|
} finally {
|
||||||
dispatch({ type: 'SET_IS_LOADING', payload: false });
|
dispatch({ type: 'SET_IS_LOADING', payload: false });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (entryId) {
|
if (entryId && isDrawerOpen) {
|
||||||
getEntry();
|
getEntry();
|
||||||
}
|
}
|
||||||
}, [entryId]);
|
}, [entryId]);
|
||||||
@@ -175,6 +175,19 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
|
|||||||
<XmarkIcon />
|
<XmarkIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
{state.error && (
|
||||||
|
<Grid display="flex" justifyContent="center" size={12}>
|
||||||
|
<Alert
|
||||||
|
onClose={() =>
|
||||||
|
dispatch({ type: 'SET_ERROR', payload: undefined })
|
||||||
|
}
|
||||||
|
severity="error"
|
||||||
|
sx={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
{state.error}
|
||||||
|
</Alert>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
<Grid alignItems="center" display="flex" size={4}>
|
<Grid alignItems="center" display="flex" size={4}>
|
||||||
<Typography variant="body1">Pilot *</Typography>
|
<Typography variant="body1">Pilot *</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -200,7 +213,17 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
|
|||||||
<Select
|
<Select
|
||||||
disabled={state.isDisabled}
|
disabled={state.isDisabled}
|
||||||
fullWidth
|
fullWidth
|
||||||
onChange={onChange}
|
onChange={(event) => {
|
||||||
|
const pilot = pilots?.find(
|
||||||
|
(pilot) => (pilot.id = event.target.value)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pilot) {
|
||||||
|
methods.setValue('pilotName', pilot.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
methods.setValue('pilotId', event.target.value);
|
||||||
|
}}
|
||||||
options={
|
options={
|
||||||
state.pilotOptions && state.pilotOptions.length > 0
|
state.pilotOptions && state.pilotOptions.length > 0
|
||||||
? state.pilotOptions
|
? state.pilotOptions
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { ILogbookEntryFormState } from './ILogbookEntryFormState';
|
import { ILogbookEntryFormState } from './ILogbookEntryFormState';
|
||||||
|
|
||||||
type Action =
|
type Action =
|
||||||
|
| { type: 'SET_ERROR'; payload: string | undefined }
|
||||||
| { type: 'SET_IS_DISABLED'; payload: boolean }
|
| { type: 'SET_IS_DISABLED'; payload: boolean }
|
||||||
| { type: 'SET_IS_LOADING'; payload: boolean }
|
| { type: 'SET_IS_LOADING'; payload: boolean }
|
||||||
| { type: 'SET_PILOT_OPTIONS'; payload: { label: string; value: string }[] }
|
| { type: 'SET_PILOT_OPTIONS'; payload: { label: string; value: string }[] }
|
||||||
| { type: 'SET_SELECTED_ENTRY_PILOT_NAME'; payload: string };
|
| { type: 'SET_SELECTED_ENTRY_PILOT_NAME'; payload: string };
|
||||||
|
|
||||||
export const initialState: ILogbookEntryFormState = {
|
export const initialState: ILogbookEntryFormState = {
|
||||||
|
error: undefined,
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
pilotOptions: [],
|
pilotOptions: [],
|
||||||
@@ -18,6 +20,12 @@ export const reducer = (
|
|||||||
action: Action
|
action: Action
|
||||||
): ILogbookEntryFormState => {
|
): ILogbookEntryFormState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case 'SET_ERROR': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
error: action.payload
|
||||||
|
};
|
||||||
|
}
|
||||||
case 'SET_IS_DISABLED': {
|
case 'SET_IS_DISABLED': {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@@ -42,5 +50,8 @@ export const reducer = (
|
|||||||
selectedEntryPilotName: action.payload
|
selectedEntryPilotName: action.payload
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
default: {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user