diff --git a/src/frontend/src/pages/FinancePage/FinanceComponents/ReimbursementRequestInfo.tsx b/src/frontend/src/pages/FinancePage/FinanceComponents/ReimbursementRequestInfo.tsx index db8c2d10e4..69d258d33e 100644 --- a/src/frontend/src/pages/FinancePage/FinanceComponents/ReimbursementRequestInfo.tsx +++ b/src/frontend/src/pages/FinancePage/FinanceComponents/ReimbursementRequestInfo.tsx @@ -1,7 +1,7 @@ import { Box, Tooltip, IconButton } from '@mui/material'; import { useLocation, useHistory, useParams } from 'react-router-dom'; import { useState, useEffect } from 'react'; -import { isGuest, ReimbursementRequest } from 'shared'; +import { equalsWbsNumber, isGuest, ReimbursementRequest, validateWBS, WBSElementData } from 'shared'; import { ReimbursementProduct, ReimbursementStatusType } from 'shared'; import { undefinedPipe, fullNamePipe, centsToDollar, datePipe, dateUndefinedPipe } from '../../../utils/pipes'; import { @@ -25,6 +25,7 @@ interface ReimbursementRequestInfoProps { statuses?: ReimbursementStatusType[]; startDate?: Date | null; endDate?: Date | null; + selectedProject?: { label: string; id: string }; onCloseSidePage: () => void; } @@ -37,6 +38,7 @@ const ReimbursementRequestInfo = ({ statuses, startDate, endDate, + selectedProject, onCloseSidePage }: ReimbursementRequestInfoProps) => { const user = useCurrentUser(); @@ -71,6 +73,22 @@ const ReimbursementRequestInfo = ({ return false; } + if (selectedProject) { + const filterWbsNum = validateWBS(selectedProject.id); + + const matchesProject = request.reimbursementProducts.some((product) => { + const reason = product.reimbursementProductReason; + if ((reason as WBSElementData).wbsNum) { + return equalsWbsNumber( + { ...(reason as WBSElementData).wbsNum, workPackageNumber: 0 }, + { ...filterWbsNum, workPackageNumber: 0 } + ); + } + return false; + }); + if (!matchesProject) return false; + } + return true; }); @@ -225,6 +243,15 @@ const ReimbursementRequestInfo = ({ const query = term.trim().toLowerCase().split(/\s+/); return query.every((q: string) => { const lowercase_query = q.toLowerCase(); + + let projectsString = ''; + for (const product of rowData.reimbursementProducts) { + const reason = product.reimbursementProductReason; + if ((reason as WBSElementData).wbsNum) { + projectsString += (reason as WBSElementData).wbsName; + } + } + return ( (rowData as any).status.toLowerCase().includes(lowercase_query) || ('' + (rowData as any).identifier).toLowerCase().includes(lowercase_query) || @@ -236,7 +263,8 @@ const ReimbursementRequestInfo = ({ ('' + (rowData as any).reimbursementProducts.map((product: any) => product.name)) .toLowerCase() .includes(lowercase_query) || - ('' + (rowData as any).description).toLowerCase().includes(lowercase_query) + ('' + (rowData as any).description).toLowerCase().includes(lowercase_query) || + projectsString.toLowerCase().includes(lowercase_query) ); }); }; diff --git a/src/frontend/src/pages/FinancePage/ReimbursementRequestsSection.tsx b/src/frontend/src/pages/FinancePage/ReimbursementRequestsSection.tsx index eb08cff4f8..734f624122 100644 --- a/src/frontend/src/pages/FinancePage/ReimbursementRequestsSection.tsx +++ b/src/frontend/src/pages/FinancePage/ReimbursementRequestsSection.tsx @@ -15,6 +15,7 @@ interface ReimbursementRequestTableProps { statuses?: ReimbursementStatusType[]; startDate?: Date | null; endDate?: Date | null; + selectedProject?: { label: string; id: string }; } const ReimbursementRequestTable = ({ @@ -24,7 +25,8 @@ const ReimbursementRequestTable = ({ onCloseSidePage, statuses, startDate, - endDate + endDate, + selectedProject }: ReimbursementRequestTableProps) => { const defaultTab = '/my-requests'; @@ -68,6 +70,7 @@ const ReimbursementRequestTable = ({ statuses={statuses} startDate={startDate} endDate={endDate} + selectedProject={selectedProject} onCloseSidePage={onCloseSidePage} /> diff --git a/src/frontend/src/pages/FinancePage/ReimbursmentRequests.tsx b/src/frontend/src/pages/FinancePage/ReimbursmentRequests.tsx index 8ac7c2e081..f01a43d4c2 100644 --- a/src/frontend/src/pages/FinancePage/ReimbursmentRequests.tsx +++ b/src/frontend/src/pages/FinancePage/ReimbursmentRequests.tsx @@ -1,4 +1,15 @@ -import { Box, Button, Menu, MenuItem, ListItemIcon, Typography, FormControlLabel, Checkbox } from '@mui/material'; +import { + Box, + Button, + Menu, + MenuItem, + ListItemIcon, + Typography, + FormControlLabel, + Checkbox, + Autocomplete, + TextField +} from '@mui/material'; import { useState } from 'react'; import AttachMoneyIcon from '@mui/icons-material/AttachMoney'; import { NERButton } from '../../components/NERButton'; @@ -6,7 +17,7 @@ import ReceiptIcon from '@mui/icons-material/Receipt'; import WorkIcon from '@mui/icons-material/Work'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import { useCurrentUser } from '../../hooks/users.hooks'; -import { isAdmin, isGuest, isHead, isLead, ReimbursementStatusType } from 'shared'; +import { isAdmin, isGuest, isHead, isLead, ReimbursementStatusType, wbsPipe } from 'shared'; import FilterListIcon from '@mui/icons-material/FilterList'; import ReimbursementRequestTable from './ReimbursementRequestsSection'; import { useToast } from '../../hooks/toasts.hooks'; @@ -20,6 +31,8 @@ import { DatePicker } from '@mui/x-date-pickers'; import ReportRefundModal from './FinanceComponents/ReportRefundModal'; import GenerateReceiptsModal from './FinanceComponents/GenerateReceiptsModal'; import ErrorPage from '../ErrorPage'; +import { useAllProjects } from '../../hooks/projects.hooks'; +import LoadingIndicator from '../../components/LoadingIndicator'; const ReimbursementRequests: React.FC = () => { const allStatuses = Object.values(ReimbursementStatusType); @@ -114,16 +127,24 @@ const ReimbursementRequests: React.FC = () => { isError: allReimbursementRequestsIsError, error: allReimbursementRequestsError } = useAllReimbursementRequests(); + const { + data: allProjects, + isLoading: allProjectsIsLoading, + isError: allProjectsIsError, + error: allProjectsError + } = useAllProjects(); const [anchorFilterEl, setAnchorFilterEl] = useState(null); const [selectedStatuses, setSelectedStatuses] = useState([]); const [startDate, setStartDate] = useState(null); const [endDate, setEndDate] = useState(null); + const [selectedProjectFilter, setSelectedProjectFilter] = useState<{ label: string; id: string } | null>(null); const clearData = () => { setSelectedStatuses([]); setStartDate(null); setEndDate(null); + setSelectedProjectFilter(null); }; const handleFilterMenuOpen = (e: React.MouseEvent) => setAnchorFilterEl(e.currentTarget); @@ -135,6 +156,13 @@ const ReimbursementRequests: React.FC = () => { if (assignedReimbursementRequestIsError) return ; if (createdReimbursementRequestIsError) return ; + const projectAutocompleteOptions = allProjects + ? allProjects.map((proj) => ({ + label: wbsPipe(proj.wbsNum) + ' - ' + proj.name, + id: wbsPipe(proj.wbsNum) + })) + : []; + const filterMenu = ( { ); })} + Filter by Project + + {allProjectsIsLoading ? ( + + ) : allProjectsIsError ? ( + + Failed to load projects {allProjectsError?.message} + + ) : ( + { + setSelectedProjectFilter(newValue); + }} + renderInput={(params) => } + /> + )} Filter by Date { statuses={selectedStatuses} startDate={startDate} endDate={endDate} + selectedProject={selectedProjectFilter ?? undefined} onCloseSidePage={() => { refetchCreatedReimbursementRequests(); refetchAllReimbursementRequests();