From 2ac780b22d105c54e04a975405a37801eaa0ac7a Mon Sep 17 00:00:00 2001 From: Justin Wang Date: Thu, 23 Apr 2026 10:09:22 -0400 Subject: [PATCH 01/12] frontend for pantry management --- apps/frontend/src/api/apiClient.ts | 7 + apps/frontend/src/app.tsx | 9 + .../src/containers/adminPantryManagement.tsx | 417 ++++++++++++++++++ apps/frontend/src/containers/homepage.tsx | 7 + apps/frontend/src/types/types.ts | 15 + 5 files changed, 455 insertions(+) create mode 100644 apps/frontend/src/containers/adminPantryManagement.tsx diff --git a/apps/frontend/src/api/apiClient.ts b/apps/frontend/src/api/apiClient.ts index b6d7fb38e..f2fb203ba 100644 --- a/apps/frontend/src/api/apiClient.ts +++ b/apps/frontend/src/api/apiClient.ts @@ -38,6 +38,7 @@ import { FoodRequestWithoutRelations, VolunteerOrder, VolunteerAction, + ApprovedPantryResponse, } from 'types/types'; const defaultBaseUrl = @@ -176,6 +177,12 @@ export class ApiClient { ) as Promise; } + public async getApprovedPantries(): Promise { + return this.get(`/api/pantries/approved`) as Promise< + ApprovedPantryResponse[] + >; + } + public async getPantryFromOrder(orderId: number): Promise { return this.axiosInstance .get(`/api/orders/${orderId}/pantry`) diff --git a/apps/frontend/src/app.tsx b/apps/frontend/src/app.tsx index 84b03edcd..383de3399 100644 --- a/apps/frontend/src/app.tsx +++ b/apps/frontend/src/app.tsx @@ -35,6 +35,7 @@ import VolunteerRequestManagement from '@containers/volunteerRequestManagement'; import AdminDonationStats from '@containers/adminDonationStats'; import ProfilePage from '@containers/profilePage'; import VolunteerOrderManagement from '@containers/volunteerOrderManagement'; +import AdminPantryManagement from '@containers/adminPantryManagement'; Amplify.configure(CognitoAuthConfig); @@ -256,6 +257,14 @@ const router = createBrowserRouter([ ), }, + { + path: '/admin-pantry-management', + element: ( + + + + ), + }, ], }, ]); diff --git a/apps/frontend/src/containers/adminPantryManagement.tsx b/apps/frontend/src/containers/adminPantryManagement.tsx new file mode 100644 index 000000000..a50be6486 --- /dev/null +++ b/apps/frontend/src/containers/adminPantryManagement.tsx @@ -0,0 +1,417 @@ +import { useEffect, useState } from 'react'; +import { + Table, + Text, + Flex, + Input, + VStack, + Box, + Pagination, + ButtonGroup, + IconButton, + Link, + Button, + Checkbox, + Badge, +} from '@chakra-ui/react'; +import { ChevronRight, ChevronLeft, Funnel, Search } from 'lucide-react'; +import { ApprovedPantryResponse } from '../types/types'; +import ApiClient from '@api/apiClient'; +import { FloatingAlert } from '@components/floatingAlert'; +import { useAlert } from '../hooks/alert'; +import { getInitials } from '@utils/utils'; +import { RefrigeratedDonation } from '../types/pantryEnums'; + +const AdminPantryManagement: React.FC = () => { + const [currentPage, setCurrentPage] = useState(1); + const [pantries, setPantries] = useState([]); + const [searchPantry, setSearchPantry] = useState(''); + + const [selectedPantries, setSelectedPantries] = useState([]); + + const [alertState, setAlertMessage] = useAlert(); + const [submitSuccess, setSubmitSuccess] = useState(false); + + const [isFilterOpen, setIsFilterOpen] = useState(false); + + const pageSize = 8; + + const USER_ICON_COLORS = ['#F89E19', '#CC3538', '#2795A5', '#2B4E60']; + + useEffect(() => { + const fetchPantries = async () => { + try { + const allApprovedPantries = await ApiClient.getApprovedPantries(); + setPantries(allApprovedPantries); + } catch { + setAlertMessage('Error fetching pantries'); + } + }; + + fetchPantries(); + }, [setAlertMessage]); + + useEffect(() => { + setCurrentPage(1); + }, [selectedPantries]); + + const pantryOptions = [...new Set(pantries.map((p) => p.pantryName))].sort( + (a, b) => a.localeCompare(b), + ); + + const handleFilterChange = (pantry: string, checked: boolean) => { + if (checked) { + setSelectedPantries([...selectedPantries, pantry]); + } else { + setSelectedPantries(selectedPantries.filter((p) => p !== pantry)); + } + }; + + const filteredPantries = pantries.filter((p) => { + const matchesFilter = + selectedPantries.length === 0 || selectedPantries.includes(p.pantryName); + return matchesFilter; + }); + + const paginatedPantries = filteredPantries.slice( + (currentPage - 1) * pageSize, + currentPage * pageSize, + ); + + return ( + + + Pantry Management + + {alertState && ( + + )} + + + + + + {isFilterOpen && ( + <> + setIsFilterOpen(false)} + zIndex={10} + /> + + + + setSearchPantry(e.target.value)} + fontSize="sm" + pl="30px" + border="none" + bg="transparent" + _focus={{ + boxShadow: 'none', + border: 'none', + outline: 'none', + }} + /> + + + {pantryOptions + .filter((pantry) => + pantry + .toLowerCase() + .includes(searchPantry.toLowerCase()), + ) + .map((pantry) => ( + + handleFilterChange(pantry, e.checked) + } + color="black" + size="sm" + > + + + {pantry} + + ))} + + + + )} + + + + + + + Pantry + + + Assignee + + + Refridgerator-Friendly + + + Action + + + + + {paginatedPantries?.map((pantry) => ( + + + + {pantry.pantryName} + + + + + {pantry.volunteers && pantry.volunteers.length > 0 ? ( + (() => { + const volunteers = pantry.volunteers; + const maxVisible = 3; + + const hasOverflow = volunteers.length > maxVisible; + const visibleVolunteers = hasOverflow + ? volunteers.slice(0, maxVisible - 1) + : volunteers; + + const remainingCount = + volunteers.length - (maxVisible - 1); + + return ( + <> + {visibleVolunteers.map((volunteer, index) => ( + + {getInitials( + volunteer.firstName, + volunteer.lastName, + )} + + ))} + + {hasOverflow && ( + + +{remainingCount} + + )} + + ); + })() + ) : ( + + No volunteer + + )} + + + + + {pantry.refrigeratedDonation === RefrigeratedDonation.YES + ? 'Refridgerator-Friendly' + : 'Not Refridgerator-Friendly'} + + + + + View Orders + + + + ))} + + + + setCurrentPage(page)} + > + + + + setCurrentPage((prev) => Math.max(prev - 1, 1)) + } + > + + + + + ( + setCurrentPage(page.value)} + > + {page.value} + + )} + /> + + + + setCurrentPage((prev) => + Math.min(prev + 1, Math.ceil(pantries.length / pageSize)), + ) + } + > + + + + + + + + + ); +}; + +export default AdminPantryManagement; diff --git a/apps/frontend/src/containers/homepage.tsx b/apps/frontend/src/containers/homepage.tsx index 15814c284..b212ce67a 100644 --- a/apps/frontend/src/containers/homepage.tsx +++ b/apps/frontend/src/containers/homepage.tsx @@ -178,6 +178,13 @@ const Homepage: React.FC = () => { + + + + Pantry Management + + + diff --git a/apps/frontend/src/types/types.ts b/apps/frontend/src/types/types.ts index ebddfb2ab..d6cee6024 100644 --- a/apps/frontend/src/types/types.ts +++ b/apps/frontend/src/types/types.ts @@ -371,6 +371,21 @@ export interface ManufacturerApplicationDto { newsletterSubscription?: boolean; } +export interface ApprovedPantryResponse { + pantryId: number; + pantryName: string; + refrigeratedDonation: RefrigeratedDonation; + volunteers: AssignedVolunteer[]; +} + +export interface AssignedVolunteer { + userId: number; + firstName: string; + lastName: string; + email: string; + phone: string; +} + export interface CreateFoodRequestBody { pantryId: number; requestedSize: RequestSize; From 73f1e0d2cf253e6a12ca5d81eed37144e8687d19 Mon Sep 17 00:00:00 2001 From: Justin Wang Date: Thu, 23 Apr 2026 17:37:55 -0400 Subject: [PATCH 02/12] assign volunteers modal --- apps/frontend/src/api/apiClient.ts | 8 + .../forms/assignVolunteersModal.tsx | 272 ++++++++++++++++++ .../src/containers/adminPantryManagement.tsx | 41 ++- apps/frontend/src/types/types.ts | 5 + 4 files changed, 317 insertions(+), 9 deletions(-) create mode 100644 apps/frontend/src/components/forms/assignVolunteersModal.tsx diff --git a/apps/frontend/src/api/apiClient.ts b/apps/frontend/src/api/apiClient.ts index f2fb203ba..a13f5ebb4 100644 --- a/apps/frontend/src/api/apiClient.ts +++ b/apps/frontend/src/api/apiClient.ts @@ -39,6 +39,7 @@ import { VolunteerOrder, VolunteerAction, ApprovedPantryResponse, + UpdatePantryVolunteersDto, } from 'types/types'; const defaultBaseUrl = @@ -404,6 +405,13 @@ export class ApiClient { }); } + public async updatePantryVolunteers( + pantryId: number, + body: UpdatePantryVolunteersDto, + ): Promise { + await this.axiosInstance.patch(`api/pantries/${pantryId}/volunteers`, body); + } + public async updateFoodManufacturer( manufacturerId: number, decision: 'approve' | 'deny', diff --git a/apps/frontend/src/components/forms/assignVolunteersModal.tsx b/apps/frontend/src/components/forms/assignVolunteersModal.tsx new file mode 100644 index 000000000..6395f9693 --- /dev/null +++ b/apps/frontend/src/components/forms/assignVolunteersModal.tsx @@ -0,0 +1,272 @@ +import ApiClient from '@api/apiClient'; +import { + Box, + Button, + Checkbox, + CloseButton, + Dialog, + Flex, + Input, + InputGroup, + Text, + VStack, +} from '@chakra-ui/react'; +import { useAlert } from '../../hooks/alert'; +import { useEffect, useState } from 'react'; +import { ApprovedPantryResponse, Assignments } from 'types/types'; +import { SearchIcon } from 'lucide-react'; +import { getInitials } from '@utils/utils'; +import { FloatingAlert } from '@components/floatingAlert'; + +interface AssignVolunteersModalProps { + pantry: ApprovedPantryResponse; + onSuccess: () => void; + onClose: () => void; + isOpen: boolean; +} + +type VolunteerDisplay = { + userId: number; + firstName: string; + lastName: string; +}; + +const USER_ICON_COLORS = ['#F89E19', '#CC3538', '#2795A5', '#2B4E60']; + +const AssignVolunteersModal: React.FC = ({ + pantry, + onSuccess, + onClose, + isOpen, +}) => { + const [alertState, setAlertMessage] = useAlert(); + + const [assignedVolunteers, setAssignedVolunteers] = useState< + VolunteerDisplay[] + >([]); + const [unassignedVolunteers, setUnassignedVolunteers] = useState< + VolunteerDisplay[] + >([]); + + const [selectedIds, setSelectedIds] = useState>(new Set()); + + const [searchName, setSearchName] = useState(''); + + const handleSearchNameChange = ( + event: React.ChangeEvent, + ) => { + setSearchName(event.target.value); + }; + + useEffect(() => { + const fetchVolunteers = async () => { + try { + const allVolunteers: Assignments[] = await ApiClient.getVolunteers(); + + const assignedIds = new Set(pantry.volunteers.map((v) => v.userId)); + + const normalized: VolunteerDisplay[] = allVolunteers.map((v) => ({ + userId: v.id, + firstName: v.firstName, + lastName: v.lastName, + })); + + const assigned = normalized.filter((v) => assignedIds.has(v.userId)); + + const unassigned = normalized.filter((v) => !assignedIds.has(v.userId)); + + setAssignedVolunteers(assigned); + setUnassignedVolunteers(unassigned); + setSelectedIds(new Set(pantry.volunteers.map((v) => v.userId))); + } catch { + setAlertMessage('Error fetching volunteers'); + } + }; + + fetchVolunteers(); + }, [pantry, setAlertMessage]); + + const allVolunteers = [...assignedVolunteers, ...unassignedVolunteers]; + + const filteredVolunteers = allVolunteers.filter((v) => { + const fullName = `${v.firstName} ${v.lastName}`.toLowerCase(); + return fullName.includes(searchName.toLowerCase()); + }); + + const handleToggle = (userId: number, checked: boolean) => { + setSelectedIds((prev) => { + const next = new Set(prev); + if (checked) next.add(userId); + else next.delete(userId); + return next; + }); + }; + + const handleSave = async () => { + try { + const originalIds = new Set(pantry.volunteers.map((v) => v.userId)); + + const addVolunteerIds = [...selectedIds].filter( + (id) => !originalIds.has(id), + ); + const removeVolunteerIds = [...originalIds].filter( + (id) => !selectedIds.has(id), + ); + + if (addVolunteerIds.length > 0 || removeVolunteerIds.length > 0) { + await ApiClient.updatePantryVolunteers(pantry.pantryId, { + addVolunteerIds, + removeVolunteerIds, + }); + } + + onSuccess(); + onClose(); + } catch { + setAlertMessage('Error saving volunteer assignments'); + } + }; + + return ( + { + if (!e.open) onClose(); + }} + closeOnInteractOutside + > + {alertState && ( + + )} + + + + + + + + + + Assign Volunteers + + + + + + {pantry.pantryName} + + + + } + > + + + + + {filteredVolunteers.map((volunteer) => ( + + + + {getInitials( + volunteer.firstName, + volunteer.lastName, + )} + + + + {volunteer.firstName} {volunteer.lastName} + + + + + handleToggle(volunteer.userId, e.checked) + } + size="md" + > + + + + + ))} + + {filteredVolunteers.length === 0 && ( + + No volunteers found + + )} + + + + + + + + + + + + ); +}; + +export default AssignVolunteersModal; diff --git a/apps/frontend/src/containers/adminPantryManagement.tsx b/apps/frontend/src/containers/adminPantryManagement.tsx index a50be6486..acff7fe04 100644 --- a/apps/frontend/src/containers/adminPantryManagement.tsx +++ b/apps/frontend/src/containers/adminPantryManagement.tsx @@ -21,6 +21,7 @@ import { FloatingAlert } from '@components/floatingAlert'; import { useAlert } from '../hooks/alert'; import { getInitials } from '@utils/utils'; import { RefrigeratedDonation } from '../types/pantryEnums'; +import AssignVolunteersModal from '@components/forms/assignVolunteersModal'; const AdminPantryManagement: React.FC = () => { const [currentPage, setCurrentPage] = useState(1); @@ -34,23 +35,36 @@ const AdminPantryManagement: React.FC = () => { const [isFilterOpen, setIsFilterOpen] = useState(false); + const [ + selectedPantryToAssignVolunteers, + setSelectedPantryToAssignVolunteers, + ] = useState(null); + const pageSize = 8; const USER_ICON_COLORS = ['#F89E19', '#CC3538', '#2795A5', '#2B4E60']; - useEffect(() => { - const fetchPantries = async () => { - try { - const allApprovedPantries = await ApiClient.getApprovedPantries(); - setPantries(allApprovedPantries); - } catch { - setAlertMessage('Error fetching pantries'); - } - }; + const fetchPantries = async () => { + try { + const allApprovedPantries = await ApiClient.getApprovedPantries(); + setPantries(allApprovedPantries); + } catch { + setSubmitSuccess(false); + + setAlertMessage('Error fetching pantries'); + } + }; + useEffect(() => { fetchPantries(); }, [setAlertMessage]); + const handleAssignVolunteersSuccess = () => { + setSubmitSuccess(true); + setAlertMessage('Successfully assigned volunteers'); + fetchPantries(); + }; + useEffect(() => { setCurrentPage(1); }, [selectedPantries]); @@ -247,6 +261,7 @@ const AdminPantryManagement: React.FC = () => { color="black" variant="underline" textDecorationColor="neutral.700" + onClick={() => setSelectedPantryToAssignVolunteers(pantry)} > {pantry.pantryName} @@ -358,6 +373,14 @@ const AdminPantryManagement: React.FC = () => { ))} + {selectedPantryToAssignVolunteers && ( + setSelectedPantryToAssignVolunteers(null)} + onSuccess={handleAssignVolunteersSuccess} + isOpen={true} + /> + )} diff --git a/apps/frontend/src/types/types.ts b/apps/frontend/src/types/types.ts index d6cee6024..467b1af74 100644 --- a/apps/frontend/src/types/types.ts +++ b/apps/frontend/src/types/types.ts @@ -59,6 +59,11 @@ export interface ConfirmDeliveryDto { feedback?: string; } +export interface UpdatePantryVolunteersDto { + addVolunteerIds?: number[]; + removeVolunteerIds?: number[]; +} + export interface PantryWithUser extends Pantry { pantryUser: User; } From 1da1fcca04ac36ce3b809088a4d5cc11af56b3de Mon Sep 17 00:00:00 2001 From: Justin Wang Date: Thu, 23 Apr 2026 18:02:33 -0400 Subject: [PATCH 03/12] format issues --- apps/frontend/src/api/apiClient.ts | 2 +- apps/frontend/src/app.tsx | 3 ++- apps/frontend/src/containers/homepage.tsx | 5 +++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/frontend/src/api/apiClient.ts b/apps/frontend/src/api/apiClient.ts index d65a0fcfe..c64beb61e 100644 --- a/apps/frontend/src/api/apiClient.ts +++ b/apps/frontend/src/api/apiClient.ts @@ -164,7 +164,7 @@ export class ApiClient { } public async getApprovedPantries(): Promise { - return this.get(`/api/pantries/approved`) as Promise< + return this.axiosInstance.get(`/api/pantries/approved`) as Promise< ApprovedPantryResponse[] >; } diff --git a/apps/frontend/src/app.tsx b/apps/frontend/src/app.tsx index 3917bbf8b..f02404c63 100644 --- a/apps/frontend/src/app.tsx +++ b/apps/frontend/src/app.tsx @@ -37,6 +37,7 @@ import ProfilePage from '@containers/profilePage'; import VolunteerOrderManagement from '@containers/volunteerOrderManagement'; import AdminPantryManagement from '@containers/adminPantryManagement'; import AdminRequestManagement from '@containers/adminRequestManagement'; +import AdminDashboard from '@containers/testAdminDashboard'; Amplify.configure(CognitoAuthConfig); @@ -206,7 +207,7 @@ const router = createBrowserRouter([ path: '/test-admin-dashboard', element: ( - + ), }, diff --git a/apps/frontend/src/containers/homepage.tsx b/apps/frontend/src/containers/homepage.tsx index 65216be1e..d31c723cf 100644 --- a/apps/frontend/src/containers/homepage.tsx +++ b/apps/frontend/src/containers/homepage.tsx @@ -182,6 +182,11 @@ const Homepage: React.FC = () => { Pantry Management + + + + + Dashboard From f2a01bb998854e41f70c900e82bae25dad3b4243 Mon Sep 17 00:00:00 2001 From: Justin Wang Date: Thu, 23 Apr 2026 18:04:24 -0400 Subject: [PATCH 04/12] fetch pantries bug fix --- apps/frontend/src/api/apiClient.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/frontend/src/api/apiClient.ts b/apps/frontend/src/api/apiClient.ts index c64beb61e..cdb3cacbf 100644 --- a/apps/frontend/src/api/apiClient.ts +++ b/apps/frontend/src/api/apiClient.ts @@ -164,9 +164,9 @@ export class ApiClient { } public async getApprovedPantries(): Promise { - return this.axiosInstance.get(`/api/pantries/approved`) as Promise< - ApprovedPantryResponse[] - >; + return this.axiosInstance + .get(`/api/pantries/approved`) + .then((response) => response.data); } public async getPantryFromOrder(orderId: number): Promise { From f283a84e094ffbcd5d6b589385512dd9e9d0fc81 Mon Sep 17 00:00:00 2001 From: Justin Wang Date: Fri, 24 Apr 2026 11:33:36 -0400 Subject: [PATCH 05/12] comments --- apps/frontend/src/api/apiClient.ts | 5 +- .../forms/assignVolunteersModal.tsx | 47 +++++++----- .../src/containers/adminPantryManagement.tsx | 72 +++++++++---------- 3 files changed, 70 insertions(+), 54 deletions(-) diff --git a/apps/frontend/src/api/apiClient.ts b/apps/frontend/src/api/apiClient.ts index cdb3cacbf..532ae75e7 100644 --- a/apps/frontend/src/api/apiClient.ts +++ b/apps/frontend/src/api/apiClient.ts @@ -408,7 +408,10 @@ export class ApiClient { pantryId: number, body: UpdatePantryVolunteersDto, ): Promise { - await this.axiosInstance.patch(`api/pantries/${pantryId}/volunteers`, body); + await this.axiosInstance.patch( + `/api/pantries/${pantryId}/volunteers`, + body, + ); } public async updateFoodManufacturer( diff --git a/apps/frontend/src/components/forms/assignVolunteersModal.tsx b/apps/frontend/src/components/forms/assignVolunteersModal.tsx index 6395f9693..893a66d35 100644 --- a/apps/frontend/src/components/forms/assignVolunteersModal.tsx +++ b/apps/frontend/src/components/forms/assignVolunteersModal.tsx @@ -31,7 +31,7 @@ type VolunteerDisplay = { lastName: string; }; -const USER_ICON_COLORS = ['#F89E19', '#CC3538', '#2795A5', '#2B4E60']; +const USER_ICON_COLORS = ['yellow.core', 'red', 'teal.ssf', 'blue.core']; const AssignVolunteersModal: React.FC = ({ pantry, @@ -157,20 +157,24 @@ const AssignVolunteersModal: React.FC = ({ fontFamily="inter" fontWeight={600} color="black" + mt={3} > Assign Volunteers - + {pantry.pantryName} + + + } + px={3} > = ({ key={volunteer.userId} align="center" justify="space-between" - py={3} borderBottom="1px solid" borderColor="neutral.100" > - + = ({ - - handleToggle(volunteer.userId, e.checked) - } - size="md" + - - - + + handleToggle(volunteer.userId, e.checked) + } + size="md" + > + + + + ))} @@ -254,6 +266,7 @@ const AssignVolunteersModal: React.FC = ({ - - - setShowApproveModal(false)} - onConfirm={handleApprove} - decision="approve" - pantryName={application.pantryName} - dateApplied={formatDate(application.dateApplied)} - /> - - setShowDenyModal(false)} - onConfirm={handleDeny} - decision="deny" - pantryName={application.pantryName} - dateApplied={formatDate(application.dateApplied)} - /> - + {isApplicationMode && ( + + + + + setShowApproveModal(false)} + onConfirm={handleApprove} + decision="approve" + pantryName={application.pantryName} + dateApplied={formatDate(application.dateApplied)} + /> + + setShowDenyModal(false)} + onConfirm={handleDeny} + decision="deny" + pantryName={application.pantryName} + dateApplied={formatDate(application.dateApplied)} + /> + + )} diff --git a/apps/frontend/src/routes.ts b/apps/frontend/src/routes.ts index 19da091e0..254a1b783 100644 --- a/apps/frontend/src/routes.ts +++ b/apps/frontend/src/routes.ts @@ -12,7 +12,8 @@ export const ROUTES = { FOOD_MANUFACTURER_APPLICATION: '/food-manufacturer-application', APPLICATION_SUBMITTED: '/application-submitted', - PANTRY_APPLICATION_DETAILS: '/pantry-application-details/:applicationId', + PANTRY_APPLICATION_DETAILS: '/pantry-details/application/:applicationId', + PANTRY_MANAGEMENT_DETAILS: '/pantry-details/pantry/:applicationId', FOOD_MANUFACTURER_APPLICATION_DETAILS: '/food-manufacturer-application-details/:applicationId', diff --git a/apps/frontend/src/utils/utils.ts b/apps/frontend/src/utils/utils.ts index 40054e0af..8232f24ea 100644 --- a/apps/frontend/src/utils/utils.ts +++ b/apps/frontend/src/utils/utils.ts @@ -24,8 +24,6 @@ export const DONATION_STATUS_COLORS: Record = [DonationStatus.FULFILLED]: TEAL_STATUS, }; -export const USER_ICON_COLORS = ['yellow.core', 'red', 'teal.ssf', 'blue.core']; - export const formatPhone = (phone?: string | null) => { if (!phone) return null; let digits = phone.replace(/\D/g, ''); @@ -108,3 +106,7 @@ export const getInitials = (first: string, last: string) => `${first[0] ?? ''}${last[0] ?? ''}`.toUpperCase(); export const ASSIGNEE_COLORS = ['yellow.core', 'red', 'teal.ssf', 'blue.ssf']; + +export const USER_ICON_COLORS = ASSIGNEE_COLORS.map((color) => + color === 'blue.ssf' ? 'blue.core' : color, +); From f8f91fef0bf21af241a45c62fc92ad968fa2e7b0 Mon Sep 17 00:00:00 2001 From: Justin Wang Date: Tue, 12 May 2026 10:17:51 -0400 Subject: [PATCH 11/12] comment --- .../frontend/src/components/dashboardCard.tsx | 4 +- .../src/containers/adminDashboard.tsx | 11 ++++- .../src/containers/adminOrderManagement.tsx | 4 +- .../src/containers/adminPantryManagement.tsx | 18 ++++---- .../containers/pantryApplicationDetails.tsx | 43 ++++++++++++------- .../containers/volunteerOrderManagement.tsx | 4 +- apps/frontend/src/routes.ts | 2 +- apps/frontend/src/utils/utils.ts | 6 +-- 8 files changed, 54 insertions(+), 38 deletions(-) diff --git a/apps/frontend/src/components/dashboardCard.tsx b/apps/frontend/src/components/dashboardCard.tsx index d78201021..cd08c459d 100644 --- a/apps/frontend/src/components/dashboardCard.tsx +++ b/apps/frontend/src/components/dashboardCard.tsx @@ -6,7 +6,7 @@ import { getInitials, ORDER_STATUS_COLORS, DONATION_STATUS_COLORS, - ASSIGNEE_COLORS, + USER_ICON_COLORS, } from '@utils/utils'; import { OrderAssignee, OrderStatus, DonationStatus } from '../types/types'; @@ -192,7 +192,7 @@ const DashboardCard: React.FC = ({ w="30px" h="30px" borderRadius="full" - bg={ASSIGNEE_COLORS[assignee.id % ASSIGNEE_COLORS.length]} + bg={USER_ICON_COLORS[assignee.id % USER_ICON_COLORS.length]} color="white" display="flex" alignItems="center" diff --git a/apps/frontend/src/containers/adminDashboard.tsx b/apps/frontend/src/containers/adminDashboard.tsx index 58edbfaeb..7ce01914e 100644 --- a/apps/frontend/src/containers/adminDashboard.tsx +++ b/apps/frontend/src/containers/adminDashboard.tsx @@ -15,6 +15,7 @@ import ApiClient from '@api/apiClient'; import { useAlert } from '../hooks/alert'; import { FloatingAlert } from '@components/floatingAlert'; import { useNavigate } from 'react-router-dom'; +import { ROUTES } from '../routes'; const AdminDashboard: React.FC = () => { const navigate = useNavigate(); @@ -117,8 +118,14 @@ const AdminDashboard: React.FC = () => { onLinkClick={() => { navigate( application.type === 'pantry' - ? `/pantry-details/pantry/${application.id}` - : `/food-manufacturer-application-details/${application.id}`, + ? ROUTES.PANTRY_MANAGEMENT_DETAILS.replace( + ':pantryId', + application.id.toString(), + ) + : ROUTES.FOOD_MANUFACTURER_APPLICATION_DETAILS.replace( + ':applicationId', + application.id.toString(), + ), ); }} /> diff --git a/apps/frontend/src/containers/adminOrderManagement.tsx b/apps/frontend/src/containers/adminOrderManagement.tsx index 1cd4896d2..84e09c3f8 100644 --- a/apps/frontend/src/containers/adminOrderManagement.tsx +++ b/apps/frontend/src/containers/adminOrderManagement.tsx @@ -26,7 +26,7 @@ import { formatDate, getInitials, ORDER_STATUS_COLORS, - ASSIGNEE_COLORS, + USER_ICON_COLORS, } from '@utils/utils'; import ApiClient from '@api/apiClient'; import { OrderStatus, OrderSummary } from '../types/types'; @@ -117,7 +117,7 @@ const AdminOrderManagement: React.FC = () => { if (order.assignee) { orderWithColor.assigneeColor = - ASSIGNEE_COLORS[order.assignee.id % ASSIGNEE_COLORS.length]; + USER_ICON_COLORS[order.assignee.id % USER_ICON_COLORS.length]; } grouped[status].push(orderWithColor); diff --git a/apps/frontend/src/containers/adminPantryManagement.tsx b/apps/frontend/src/containers/adminPantryManagement.tsx index e088bcdfe..303b80b37 100644 --- a/apps/frontend/src/containers/adminPantryManagement.tsx +++ b/apps/frontend/src/containers/adminPantryManagement.tsx @@ -30,15 +30,16 @@ const AdminPantryManagement: React.FC = () => { const [currentPage, setCurrentPage] = useState(1); const [pantries, setPantries] = useState([]); + + // The pantry searched in the filter const [searchPantry, setSearchPantry] = useState(''); + // The pantries selected in the filter const [selectedPantries, setSelectedPantries] = useState([]); const [alertState, setAlertMessage] = useAlert(); - const [submitSuccess, setSubmitSuccess] = useState(false); - + const [isAlertSuccess, setIsAlertSuccess] = useState(false); const [isFilterOpen, setIsFilterOpen] = useState(false); - const [ selectedPantryToAssignVolunteers, setSelectedPantryToAssignVolunteers, @@ -51,8 +52,7 @@ const AdminPantryManagement: React.FC = () => { const allApprovedPantries = await ApiClient.getApprovedPantries(); setPantries(allApprovedPantries); } catch { - setSubmitSuccess(false); - + setIsAlertSuccess(false); setAlertMessage('Error fetching pantries'); } }; @@ -62,7 +62,7 @@ const AdminPantryManagement: React.FC = () => { }, [setAlertMessage]); const handleAssignVolunteersSuccess = () => { - setSubmitSuccess(true); + setIsAlertSuccess(true); setAlertMessage('Successfully assigned volunteers'); fetchPantries(); }; @@ -103,14 +103,14 @@ const AdminPantryManagement: React.FC = () => { return ( - + Pantry Management {alertState && ( )} @@ -260,7 +260,7 @@ const AdminPantryManagement: React.FC = () => { onClick={() => navigate( ROUTES.PANTRY_MANAGEMENT_DETAILS.replace( - ':applicationId', + ':pantryId', pantry.pantryId.toString(), ), ) diff --git a/apps/frontend/src/containers/pantryApplicationDetails.tsx b/apps/frontend/src/containers/pantryApplicationDetails.tsx index 2db75ba7d..e9860b039 100644 --- a/apps/frontend/src/containers/pantryApplicationDetails.tsx +++ b/apps/frontend/src/containers/pantryApplicationDetails.tsx @@ -84,7 +84,12 @@ const EmptyState: React.FC = ({ }; const PantryApplicationDetails: React.FC = () => { - const { applicationId } = useParams<{ applicationId: string }>(); + const { applicationId, pantryId } = useParams<{ + applicationId?: string; + pantryId?: string; + }>(); + + const id = applicationId ?? pantryId; const isApplicationMode = useMatch(ROUTES.PANTRY_APPLICATION_DETAILS); @@ -127,16 +132,16 @@ const PantryApplicationDetails: React.FC = () => { const fetchApplicationDetails = useCallback(async () => { try { setLoading(true); - if (!applicationId) { + if (!id) { setError({ type: 'invalid', message: 'Application ID not provided.' }); return; - } else if (isNaN(parseInt(applicationId, 10))) { + } else if (isNaN(parseInt(id, 10))) { setError({ type: 'invalid', message: 'Application ID is not a number.', }); } - const data = await ApiClient.getPantry(parseInt(applicationId, 10)); + const data = await ApiClient.getPantry(parseInt(id, 10)); if (!data) { setError({ type: 'not_found', @@ -156,7 +161,7 @@ const PantryApplicationDetails: React.FC = () => { } finally { setLoading(false); } - }, [applicationId]); + }, [id]); useEffect(() => { fetchApplicationDetails(); @@ -255,17 +260,25 @@ const PantryApplicationDetails: React.FC = () => { p={6} boxShadow="sm" > - + - - Application #{application.pantryId} - - - {application.pantryName} - - - Applied {formatDate(application.dateApplied)} - + {isApplicationMode ? ( + <> + + Application #{application.pantryId} + + + {application.pantryName} + + + Applied {formatDate(application.dateApplied)} + + + ) : ( + + {application.pantryName} + + )} diff --git a/apps/frontend/src/containers/volunteerOrderManagement.tsx b/apps/frontend/src/containers/volunteerOrderManagement.tsx index 813f73cdb..f25f453fe 100644 --- a/apps/frontend/src/containers/volunteerOrderManagement.tsx +++ b/apps/frontend/src/containers/volunteerOrderManagement.tsx @@ -27,7 +27,7 @@ import { formatDate, getInitials, ORDER_STATUS_COLORS, - ASSIGNEE_COLORS, + USER_ICON_COLORS, } from '@utils/utils'; import ApiClient from '@api/apiClient'; import { @@ -142,7 +142,7 @@ const VolunteerOrderManagement: React.FC = () => { if (order.assignee) { orderWithColor.assigneeColor = - ASSIGNEE_COLORS[order.assignee.id % ASSIGNEE_COLORS.length]; + USER_ICON_COLORS[order.assignee.id % USER_ICON_COLORS.length]; } grouped[status].push(orderWithColor); diff --git a/apps/frontend/src/routes.ts b/apps/frontend/src/routes.ts index 254a1b783..671070660 100644 --- a/apps/frontend/src/routes.ts +++ b/apps/frontend/src/routes.ts @@ -13,7 +13,7 @@ export const ROUTES = { APPLICATION_SUBMITTED: '/application-submitted', PANTRY_APPLICATION_DETAILS: '/pantry-details/application/:applicationId', - PANTRY_MANAGEMENT_DETAILS: '/pantry-details/pantry/:applicationId', + PANTRY_MANAGEMENT_DETAILS: '/pantry-details/pantry/:pantryId', FOOD_MANUFACTURER_APPLICATION_DETAILS: '/food-manufacturer-application-details/:applicationId', diff --git a/apps/frontend/src/utils/utils.ts b/apps/frontend/src/utils/utils.ts index 8232f24ea..98497357a 100644 --- a/apps/frontend/src/utils/utils.ts +++ b/apps/frontend/src/utils/utils.ts @@ -105,8 +105,4 @@ export const generateNextDonationDate = ( export const getInitials = (first: string, last: string) => `${first[0] ?? ''}${last[0] ?? ''}`.toUpperCase(); -export const ASSIGNEE_COLORS = ['yellow.core', 'red', 'teal.ssf', 'blue.ssf']; - -export const USER_ICON_COLORS = ASSIGNEE_COLORS.map((color) => - color === 'blue.ssf' ? 'blue.core' : color, -); +export const USER_ICON_COLORS = ['yellow.core', 'red', 'teal.ssf', 'blue.core']; From 0ad59edc9470385cf221ea7b266ab009a45d14ba Mon Sep 17 00:00:00 2001 From: Justin Wang Date: Fri, 15 May 2026 12:05:43 -0400 Subject: [PATCH 12/12] comments --- apps/frontend/src/containers/adminPantryManagement.tsx | 4 ++-- apps/frontend/src/containers/pantryApplicationDetails.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/frontend/src/containers/adminPantryManagement.tsx b/apps/frontend/src/containers/adminPantryManagement.tsx index 303b80b37..e807b5ce4 100644 --- a/apps/frontend/src/containers/adminPantryManagement.tsx +++ b/apps/frontend/src/containers/adminPantryManagement.tsx @@ -309,7 +309,7 @@ const AdminPantryManagement: React.FC = () => { justifyContent="center" color="white" fontSize="12px" - ml={index === 0 ? 0 : '-10px'} + ml={index === 0 ? 0 : '-4px'} zIndex={index} border="1px solid white" > @@ -331,7 +331,7 @@ const AdminPantryManagement: React.FC = () => { justifyContent="center" color="neutral.50" textStyle="p2" - ml="-10px" + ml="-4px" zIndex={maxVisible} border="1px solid white" > diff --git a/apps/frontend/src/containers/pantryApplicationDetails.tsx b/apps/frontend/src/containers/pantryApplicationDetails.tsx index e9860b039..5838b7336 100644 --- a/apps/frontend/src/containers/pantryApplicationDetails.tsx +++ b/apps/frontend/src/containers/pantryApplicationDetails.tsx @@ -260,7 +260,7 @@ const PantryApplicationDetails: React.FC = () => { p={6} boxShadow="sm" > - + {isApplicationMode ? ( <> @@ -275,7 +275,7 @@ const PantryApplicationDetails: React.FC = () => { ) : ( - + {application.pantryName} )}