Skip to content

Commit e754720

Browse files
authored
Merge pull request #3820 from Northeastern-Electric-Racing/rr-improvements
Misc. RR Improvements
2 parents be417a9 + 7497098 commit e754720

17 files changed

Lines changed: 898 additions & 1258 deletions

src/backend/src/services/reimbursement-requests.services.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ export default class ReimbursementRequestService {
242242
dateOfExpense?: Date
243243
): Promise<ReimbursementRequest> {
244244
if (await userHasPermission(recipient.userId, organization.organizationId, isGuest))
245-
throw new AccessDeniedGuestException('Guests cannot create a reimbursement request');
245+
throw new AccessDeniedGuestException('create a reimbursement request');
246246

247247
if (!recipient.userSecureSettings) throw new HttpException(500, 'User does not have their finance settings set up');
248248

src/frontend/src/components/NERDataGrid.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ interface NERDataGridProps<T> {
1414
pageSizeDefault?: number;
1515
rowsPerPageOptions?: number[];
1616
onAdd: () => void;
17+
addLabel?: string; // optional label for the add/create button (defaults to 'Add')
1718
onRowClick?: (item: T) => void;
1819
// optional simple search fields (keys of mapped row) or a custom filter function
1920
searchFields?: (keyof MapRowResult<T>)[];
@@ -33,6 +34,7 @@ function NERDataGrid<T>({
3334
pageSizeDefault = 10,
3435
rowsPerPageOptions,
3536
onAdd,
37+
addLabel = 'Add',
3638
onRowClick,
3739
searchFields,
3840
searchFilter,
@@ -96,7 +98,7 @@ function NERDataGrid<T>({
9698
sx={{ flex: 1 }}
9799
/>
98100
<Button variant="contained" size="small" onClick={onAdd} sx={{ ml: 1 }}>
99-
Add
101+
{addLabel}
100102
</Button>
101103
</Box>
102104

src/frontend/src/pages/FinancePage/CreateReimbursementRequest.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* This file is part of NER's FinishLine and licensed under GNU AGPLv3.
33
* See the LICENSE file in the repository root folder for details.
44
*/
5-
import LoadingIndicator from '../../components/LoadingIndicator';
5+
import { useHistory } from 'react-router-dom';
66
import PageLayout from '../../components/PageLayout';
77
import { useCreateReimbursementRequest, useUploadManyReceipts } from '../../hooks/finance.hooks';
88
import { routes } from '../../utils/routes';
@@ -14,8 +14,7 @@ const CreateReimbursementRequestPage: React.FC = () => {
1414
const { isLoading: createReimbursementRequestIsLoading, mutateAsync: createReimbursementRequest } =
1515
useCreateReimbursementRequest();
1616
const { isLoading: receiptsIsLoading, mutateAsync: uploadReceipts } = useUploadManyReceipts();
17-
18-
if (createReimbursementRequestIsLoading || receiptsIsLoading) return <LoadingIndicator />;
17+
const history = useHistory();
1918

2019
const onSubmit = async (data: ReimbursementRequestDataSubmission): Promise<string> => {
2120
const { reimbursementRequestId } = await createReimbursementRequest({ ...data, indexCodeId: data.indexCodeId! });
@@ -26,12 +25,23 @@ const CreateReimbursementRequestPage: React.FC = () => {
2625
return reimbursementRequestId;
2726
};
2827

28+
const onCancel = () => {
29+
history.goBack();
30+
};
31+
32+
const isSubmitting = createReimbursementRequestIsLoading || receiptsIsLoading;
33+
2934
return (
3035
<PageLayout
3136
title="Create Reimbursement Request"
32-
previousPages={[{ name: 'Reimbursement Requests', route: routes.FINANCE }]}
37+
previousPages={[{ name: 'Reimbursement Requests', route: routes.REIMBURSEMENT_REQUESTS }]}
3338
>
34-
<ReimbursementRequestForm submitText="Submit" submitData={onSubmit} />
39+
<ReimbursementRequestForm
40+
onFormExit={onCancel}
41+
submitText="Submit"
42+
submitData={onSubmit}
43+
isSubmitting={isSubmitting}
44+
/>
3545
</PageLayout>
3646
);
3747
};

src/frontend/src/pages/FinancePage/EditReimbursementRequest/EditReimbursementRequest.tsx

Lines changed: 67 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,92 @@
55
import { ReimbursementRequestDataSubmission } from '../ReimbursementRequestForm/ReimbursementRequestForm';
66
import {
77
useEditReimbursementRequest,
8+
useLeadershipApproveReimbursementRequest,
9+
useMarkPendingFinance,
810
useSingleReimbursementRequest,
911
useUploadManyReceipts
1012
} from '../../../hooks/finance.hooks';
11-
import { useParams } from 'react-router-dom';
13+
import { useHistory, useParams } from 'react-router-dom';
1214
import LoadingIndicator from '../../../components/LoadingIndicator';
1315
import ErrorPage from '../../ErrorPage';
1416
import EditReimbursementRequestRenderedDefaultValues from './EditReimbursementRequestRenderedDefaultValues';
17+
import { useToast } from '../../../hooks/toasts.hooks';
18+
import { isHead } from 'shared';
19+
import { useCurrentUser } from '../../../hooks/users.hooks';
20+
import { isReimbursementRequestLeadershipApproved } from '../../../utils/reimbursement-request.utils';
1521

16-
const EditReimbursementRequestPage: React.FC<{
17-
onSubmitEditData: (data: ReimbursementRequestDataSubmission) => Promise<string>;
18-
onExitEditPage: () => void;
19-
onSubmitToFinance?: (data: ReimbursementRequestDataSubmission) => Promise<void>;
20-
}> = ({ onSubmitEditData, onExitEditPage, onSubmitToFinance }) => {
22+
const EditReimbursementRequestPage: React.FC<{}> = () => {
2123
const { id } = useParams<{ id: string }>();
24+
const history = useHistory();
2225

23-
const { isLoading: editReimbursementRequestIsLoading } = useEditReimbursementRequest(id);
24-
const { isLoading: uploadReceiptsIsLoading } = useUploadManyReceipts();
26+
const { isLoading: editReimbursementRequestIsLoading, mutateAsync: editReimbursementRequest } =
27+
useEditReimbursementRequest(id);
28+
const { isLoading: uploadReceiptsIsLoading, mutateAsync: uploadReceipts } = useUploadManyReceipts();
2529
const { isLoading: getIsLoading, isError, error, data: reimbursementRequest } = useSingleReimbursementRequest(id);
30+
const { mutateAsync: markPendingFinance } = useMarkPendingFinance(id);
31+
const { mutateAsync: leadershipApproveReimbursementRequest } = useLeadershipApproveReimbursementRequest(id);
32+
const user = useCurrentUser();
33+
const toast = useToast();
2634

2735
if (isError) return <ErrorPage error={error} />;
2836

29-
if (getIsLoading || editReimbursementRequestIsLoading || uploadReceiptsIsLoading || !reimbursementRequest)
30-
return <LoadingIndicator />;
37+
if (getIsLoading || !reimbursementRequest) return <LoadingIndicator />;
38+
39+
const isSubmitting = editReimbursementRequestIsLoading || uploadReceiptsIsLoading;
40+
41+
const onCancel = () => {
42+
history.goBack();
43+
};
44+
45+
const onSubmitEditData = async (data: ReimbursementRequestDataSubmission): Promise<string> => {
46+
const filesToKeep = data.receiptFiles.filter((file) => file.googleFileId !== '');
47+
try {
48+
await editReimbursementRequest({ ...data, receiptPictures: filesToKeep, indexCodeId: data.indexCodeId! });
49+
await uploadReceipts({
50+
id: reimbursementRequest.reimbursementRequestId,
51+
files: data.receiptFiles.filter((receipt) => receipt.googleFileId === '').map((file) => file.file!)
52+
});
53+
toast.success('Reimbursement request updated successfully.');
54+
history.goBack();
55+
} catch (error) {
56+
if (error instanceof Error) {
57+
toast.error(`Failed to update reimbursement request: ${error.message}`);
58+
}
59+
}
60+
61+
return reimbursementRequest.reimbursementRequestId;
62+
};
63+
64+
const onSubmitToFinance = async (data: ReimbursementRequestDataSubmission) => {
65+
const filesToKeep = data.receiptFiles.filter((file) => file.googleFileId !== '');
66+
try {
67+
await editReimbursementRequest({ ...data, receiptPictures: filesToKeep, indexCodeId: data.indexCodeId! });
68+
await uploadReceipts({
69+
id: reimbursementRequest.reimbursementRequestId,
70+
files: data.receiptFiles.filter((receipt) => receipt.googleFileId === '').map((file) => file.file!)
71+
});
72+
73+
if (isHead(user.role) && !isReimbursementRequestLeadershipApproved(reimbursementRequest)) {
74+
await leadershipApproveReimbursementRequest();
75+
}
76+
77+
await markPendingFinance();
78+
toast.success('Reimbursement request submitted to Finance.');
79+
history.goBack();
80+
} catch (error) {
81+
if (error instanceof Error) {
82+
toast.error(`Failed to submit reimbursement request to Finance: ${error.message}`);
83+
}
84+
}
85+
};
3186

3287
return (
3388
<EditReimbursementRequestRenderedDefaultValues
3489
reimbursementRequest={reimbursementRequest}
3590
onSubmitData={onSubmitEditData}
36-
onExitEditPage={onExitEditPage}
91+
onExitEditPage={onCancel}
3792
onSubmitToFinance={onSubmitToFinance}
93+
isSubmitting={isSubmitting}
3894
/>
3995
);
4096
};

src/frontend/src/pages/FinancePage/EditReimbursementRequest/EditReimbursementRequestRenderedDefaultValues.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ const EditReimbursementRequestRenderedDefaultValues: React.FC<{
1212
onSubmitData: (data: ReimbursementRequestDataSubmission) => Promise<string>;
1313
onExitEditPage: () => void;
1414
onSubmitToFinance?: (data: ReimbursementRequestDataSubmission) => Promise<void>;
15-
}> = ({ reimbursementRequest, onSubmitData, onExitEditPage, onSubmitToFinance }) => {
15+
isSubmitting?: boolean;
16+
}> = ({ reimbursementRequest, onSubmitData, onExitEditPage, onSubmitToFinance, isSubmitting }) => {
1617
const previousPage = `${routes.REIMBURSEMENT_REQUESTS}/my-requests/${reimbursementRequest.reimbursementRequestId}`;
1718
const isLeadershipApproved = isReimbursementRequestLeadershipApproved(reimbursementRequest);
1819

@@ -35,6 +36,7 @@ const EditReimbursementRequestRenderedDefaultValues: React.FC<{
3536
submitData={onSubmitData}
3637
isLeadershipApproved={isLeadershipApproved}
3738
onSubmitToFinance={onSubmitToFinance}
39+
isSubmitting={isSubmitting}
3840
defaultValues={{
3941
vendorId: reimbursementRequest.vendor.vendorId,
4042
indexCodeId: reimbursementRequest.indexCode.indexCodeId,

src/frontend/src/pages/FinancePage/Finance.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@ import { routes } from '../../utils/routes';
77
import FinanceDashboard from './FinanceDashboard/FinanceDashboard';
88
import ReimbursmentRequests from './ReimbursmentRequests';
99
import CompaniesPage from './CompaniesPage';
10+
import CreateReimbursementRequestPage from './CreateReimbursementRequest';
11+
import EditReimbursementRequestPage from './EditReimbursementRequest/EditReimbursementRequest';
1012

1113
// Redirect is used since the default display page for the table should be 'my-requests'.
1214
// It also allows the side page to stay highlighted for both tabs of the table
1315
// by directing to just 'reimbursement-requests' and having the redirect occur
1416
const Finance: React.FC = () => {
1517
return (
1618
<Switch>
17-
<Route path={routes.REIMBURSEMENT_REQUEST_EDIT} component={ReimbursmentRequests} />
18-
<Route path={routes.NEW_REIMBURSEMENT_REQUEST} component={ReimbursmentRequests} />
19+
<Route path={routes.NEW_REIMBURSEMENT_REQUEST} component={CreateReimbursementRequestPage} />
20+
<Route path={routes.REIMBURSEMENT_REQUEST_EDIT} component={EditReimbursementRequestPage} />
1921
<Route path={routes.REIMBURSEMENT_REQUEST_BY_ID} component={ReimbursmentRequests} />
2022
<Route path={routes.FINANCE_DASHBOARD} component={FinanceDashboard} />
2123
<Route

0 commit comments

Comments
 (0)