From 0628a9875ecebfeec67d36531287b958629a6d4c Mon Sep 17 00:00:00 2001 From: bhuvanh66 Date: Tue, 2 Dec 2025 19:00:36 -0500 Subject: [PATCH 1/2] initial frontend assigned pantries implementation --- apps/frontend/src/app.tsx | 5 + .../containers/volunteerAssignedPantries.tsx | 441 ++++++++++++++++++ .../src/types/volunteerAssignments.ts | 18 + 3 files changed, 464 insertions(+) create mode 100644 apps/frontend/src/containers/volunteerAssignedPantries.tsx create mode 100644 apps/frontend/src/types/volunteerAssignments.ts diff --git a/apps/frontend/src/app.tsx b/apps/frontend/src/app.tsx index 89e0fccf..625bd25d 100644 --- a/apps/frontend/src/app.tsx +++ b/apps/frontend/src/app.tsx @@ -22,6 +22,7 @@ import DonationManagement from '@containers/donationManagement'; import AdminDonation from '@containers/adminDonation'; import { pantryIdLoader } from '@loaders/pantryIdLoader'; import Homepage from '@containers/homepage'; +import AssignedPantries from '@containers/volunteerAssignedPantries' const router = createBrowserRouter([ { @@ -96,6 +97,10 @@ const router = createBrowserRouter([ path: '/volunteer-management', element: , }, + { + path: '/volunteer-assigned-pantries', + element: , + }, ], }, ]); diff --git a/apps/frontend/src/containers/volunteerAssignedPantries.tsx b/apps/frontend/src/containers/volunteerAssignedPantries.tsx new file mode 100644 index 00000000..665949aa --- /dev/null +++ b/apps/frontend/src/containers/volunteerAssignedPantries.tsx @@ -0,0 +1,441 @@ +import React, { useState, useEffect } from 'react'; +import { Funnel } from 'lucide-react'; +import { + Box, + Button, + Table, + Heading, + VStack, + Checkbox, + Text, +} from '@chakra-ui/react'; +import ApiClient from '@api/apiClient'; +import { Pantry } from 'types/types'; +import { RefrigeratedDonation } from '../types/pantryEnums'; +import { Assignments } from 'types/volunteerAssignments'; + +const AssignedPantries: React.FC = () => { + const [assignments, setAssignments] = useState([]); + const [filteredAssignments, setFilteredAssignments] = useState([]); + const [pantryDetails, setPantryDetails] = useState>(new Map()); + const [isFilterOpen, setIsFilterOpen] = useState(false); + const [filterRefrigeratorFriendly, setFilterRefrigeratorFriendly] = useState(null); + const [selectedPantry, setSelectedPantry] = useState(null); + + useEffect(() => { + const fetchAssignments = async () => { + try { + + const data = await ApiClient.getAllAssignments() as Assignments[]; + setAssignments(data); + setFilteredAssignments(data); + + const detailsMap = new Map(); + await Promise.all( + data + .filter(assignment => assignment.pantry) + .map(async (assignment) => { + try { + const pantry = await ApiClient.getPantry(assignment.pantry!.pantryId); + detailsMap.set(assignment.pantry!.pantryId, pantry); + } catch (error) { + console.error(`Error fetching pantry ${assignment.pantry!.pantryId}:`, error); + } + }) + ); + setPantryDetails(detailsMap); + } catch (error) { + console.error('Error fetching assignments:', error); + alert('Error fetching assigned pantries: ' + error); + } + }; + + fetchAssignments(); + }, []); + + useEffect(() => { + + let filtered = [...assignments]; + + if (filterRefrigeratorFriendly !== null) { + filtered = filtered.filter(assignment => { + if (!assignment.pantry) return false; + const pantry = pantryDetails.get(assignment.pantry.pantryId); + if (!pantry) return true; + const isRefrigeratorFriendlyValue = + pantry.refrigeratedDonation === RefrigeratedDonation.YES || + pantry.refrigeratedDonation === RefrigeratedDonation.SOMETIMES; + return isRefrigeratorFriendlyValue === filterRefrigeratorFriendly; + }); + } + + setFilteredAssignments(filtered); + }, [filterRefrigeratorFriendly, assignments, pantryDetails]); + + const handlePantryClick = async (pantryId: number) => { + // TODO: navigate to pantr details page + try { + const fullPantryDetails = await ApiClient.getPantry(pantryId); + setSelectedPantry(fullPantryDetails); + } catch (error) { + console.error('Error fetching pantry details:', error); + alert('Error fetching pantry details: ' + error); + } + }; + + const handleViewOrders = (pantryId: number) => { + // TODO: Redirect to Order Management page when it's created + console.log('View orders for pantry:', pantryId); + }; + + const isRefrigeratorFriendly = (pantryId: number): boolean => { + const pantry = pantryDetails.get(pantryId); + if (!pantry) return false; + return pantry.refrigeratedDonation === RefrigeratedDonation.YES || + pantry.refrigeratedDonation === RefrigeratedDonation.SOMETIMES; + }; + + const getRefrigeratorFriendlyText = (pantryId: number): string => { + const pantry = pantryDetails.get(pantryId); + if (!pantry) return 'Loading...'; + if (pantry.refrigeratedDonation === RefrigeratedDonation.YES || + pantry.refrigeratedDonation === RefrigeratedDonation.SOMETIMES) { + return 'Refrigerator-Friendly'; + } + return 'Not Refrigerator-Friendly'; + }; + + const tableHeaderStyles = { + borderBottom: '1px solid', + borderColor: 'neutral.100', + color: 'neutral.800', + fontFamily: 'inter', + fontWeight: '600', + fontSize: 'sm', + }; + + return ( + + + Assigned Pantries + + + {/* Filter Button */} + + + + + {isFilterOpen && ( + <> + setIsFilterOpen(false)} + zIndex={10} + /> + + + + + setFilterRefrigeratorFriendly(e.checked ? true : null) + } + color="black" + size="sm" + > + + + + setFilterRefrigeratorFriendly(filterRefrigeratorFriendly === true ? null : true)}> + Refrigerator-Friendly Only + + + + + + setFilterRefrigeratorFriendly(e.checked ? false : null) + } + color="black" + size="sm" + > + + + + setFilterRefrigeratorFriendly(filterRefrigeratorFriendly === false ? null : false)}> + Not Refrigerator-Friendly Only + + + + + + )} + + + + {/* Pantries Table */} + + + + + Pantry + + + Refrigerator-Friendly + + + Action + + + + + {filteredAssignments.map((assignment) => { + + if (!assignment.pantry) return null; + + return ( + + + + + + + {assignment.pantry ? getRefrigeratorFriendlyText(assignment.pantry.pantryId) : 'N/A'} + + + + + + + ); + })} + + + + {/* Temporary: Show selected pantry details (will be a separate page later) */} + {selectedPantry && ( + + + Pantry Information + + + Note: This is a temporary view. A dedicated pantry details page will be created later. + + + + Pantry Name: + {selectedPantry.pantryName} + + + + Address: + + {selectedPantry.addressLine1}
+ {selectedPantry.addressLine2 && <>{selectedPantry.addressLine2}
} + {selectedPantry.addressCity}, {selectedPantry.addressState} {selectedPantry.addressZip} + {selectedPantry.addressCountry && <>
{selectedPantry.addressCountry}} +
+
+ + + Status: + {selectedPantry.status} + + + + Date Applied: + {new Date(selectedPantry.dateApplied).toLocaleDateString()} + + + + Approximately how many allergen-avoidant clients does your pantry serve? + {selectedPantry.allergenClients} + + + {selectedPantry.restrictions && selectedPantry.restrictions.length > 0 && ( + + Which food allergies or other medical dietary restrictions do clients at your pantry report? + {selectedPantry.restrictions.join(', ')} + + )} + + + Would you be able to accept refrigerated/frozen donations from us? + {selectedPantry.refrigeratedDonation} + + + + Are you willing to reserve our food shipments for allergen-avoidant individuals? + {selectedPantry.reserveFoodForAllergic} + + + {selectedPantry.reservationExplanation && ( + + Reservation Explanation: + {selectedPantry.reservationExplanation} + + )} + + + Do you have a dedicated shelf or section of your pantry for allergy-friendly items? + + {selectedPantry.dedicatedAllergyFriendly + ? 'Yes, we have a dedicated shelf or box' + : 'No, we keep allergy-friendly items throughout the pantry, depending on the type of item'} + + + + {selectedPantry.clientVisitFrequency && ( + + How often do allergen-avoidant clients visit your food pantry? + {selectedPantry.clientVisitFrequency} + + )} + + {selectedPantry.identifyAllergensConfidence && ( + + Are you confident in identifying the top 9 allergens in an ingredient list? + {selectedPantry.identifyAllergensConfidence} + + )} + + {selectedPantry.serveAllergicChildren && ( + + Do you serve allergen-avoidant or food-allergic children at your pantry? + {selectedPantry.serveAllergicChildren} + + )} + + {selectedPantry.activities && selectedPantry.activities.length > 0 && ( + + What activities are you open to doing with SSF? + {selectedPantry.activities.join(', ')} + + )} + + {selectedPantry.activitiesComments && ( + + Activities Comments: + {selectedPantry.activitiesComments} + + )} + + + What types of allergen-free items, if any, do you currently have in stock? + {selectedPantry.itemsInStock} + + + + Do allergen-avoidant clients at your pantry ever request a greater variety of items or not have enough options? + {selectedPantry.needMoreOptions} + + + + Would you like to subscribe to our quarterly newsletter? + {selectedPantry.newsletterSubscription ? 'Yes' : 'No'} + +
+ +
+ )} +
+ ); +}; + +export default AssignedPantries; \ No newline at end of file diff --git a/apps/frontend/src/types/volunteerAssignments.ts b/apps/frontend/src/types/volunteerAssignments.ts new file mode 100644 index 00000000..73d43f4f --- /dev/null +++ b/apps/frontend/src/types/volunteerAssignments.ts @@ -0,0 +1,18 @@ +export interface LimitedPantryInfo { + pantryId: number; + pantryName: string; +} + +export interface Assignments { + assignmentId: number; + volunteer: { + id: number; + firstName: string; + lastName: string; + email: string; + phone: string; + role: string; + }; + pantry: LimitedPantryInfo | null; +} + From 8fd4520c694fe895714e1d059909ce71cda8e4f7 Mon Sep 17 00:00:00 2001 From: bhuvanh66 Date: Wed, 3 Dec 2025 00:18:31 -0500 Subject: [PATCH 2/2] cleanup and added page link to homepage --- apps/frontend/src/containers/homepage.tsx | 5 + .../containers/volunteerAssignedPantries.tsx | 154 +----------------- 2 files changed, 6 insertions(+), 153 deletions(-) diff --git a/apps/frontend/src/containers/homepage.tsx b/apps/frontend/src/containers/homepage.tsx index a753762b..c39cca69 100644 --- a/apps/frontend/src/containers/homepage.tsx +++ b/apps/frontend/src/containers/homepage.tsx @@ -133,6 +133,11 @@ const Homepage: React.FC = () => { Pantry Overview + + + Volunteer Assigned Pantries + +
diff --git a/apps/frontend/src/containers/volunteerAssignedPantries.tsx b/apps/frontend/src/containers/volunteerAssignedPantries.tsx index 665949aa..28a6f1f5 100644 --- a/apps/frontend/src/containers/volunteerAssignedPantries.tsx +++ b/apps/frontend/src/containers/volunteerAssignedPantries.tsx @@ -20,7 +20,6 @@ const AssignedPantries: React.FC = () => { const [pantryDetails, setPantryDetails] = useState>(new Map()); const [isFilterOpen, setIsFilterOpen] = useState(false); const [filterRefrigeratorFriendly, setFilterRefrigeratorFriendly] = useState(null); - const [selectedPantry, setSelectedPantry] = useState(null); useEffect(() => { const fetchAssignments = async () => { @@ -72,16 +71,6 @@ const AssignedPantries: React.FC = () => { setFilteredAssignments(filtered); }, [filterRefrigeratorFriendly, assignments, pantryDetails]); - const handlePantryClick = async (pantryId: number) => { - // TODO: navigate to pantr details page - try { - const fullPantryDetails = await ApiClient.getPantry(pantryId); - setSelectedPantry(fullPantryDetails); - } catch (error) { - console.error('Error fetching pantry details:', error); - alert('Error fetching pantry details: ' + error); - } - }; const handleViewOrders = (pantryId: number) => { // TODO: Redirect to Order Management page when it's created @@ -252,7 +241,7 @@ const AssignedPantries: React.FC = () => { - - )} ); };