From 83debdd7476187c7d58021c249d3d7e939eed847 Mon Sep 17 00:00:00 2001 From: Justin Wang Date: Wed, 1 Jul 2026 15:08:34 -0400 Subject: [PATCH 1/5] changes --- .../src/volunteers/volunteers.service.spec.ts | 53 ++++++- .../src/volunteers/volunteers.service.ts | 1 + apps/frontend/src/api/apiClient.ts | 8 + apps/frontend/src/chakra-ui.d.ts | 2 +- apps/frontend/src/components/Navbar.tsx | 6 +- .../components/forms/addNewVolunteerModal.tsx | 4 +- .../forms/assignVolunteersModal.tsx | 13 +- .../components/forms/confirmActionModal.tsx | 80 ++++++++++ apps/frontend/src/containers/homepage.tsx | 2 +- apps/frontend/src/containers/loginPage.tsx | 7 + .../src/containers/volunteerManagement.tsx | 141 ++++++++++++++---- 11 files changed, 269 insertions(+), 48 deletions(-) create mode 100644 apps/frontend/src/components/forms/confirmActionModal.tsx diff --git a/apps/backend/src/volunteers/volunteers.service.spec.ts b/apps/backend/src/volunteers/volunteers.service.spec.ts index ab4b95679..8d09e34a1 100644 --- a/apps/backend/src/volunteers/volunteers.service.spec.ts +++ b/apps/backend/src/volunteers/volunteers.service.spec.ts @@ -145,7 +145,7 @@ describe('VolunteersService', () => { }); describe('getVolunteersAndPantryAssignments', () => { - it('returns an empty array when there are no volunteers', async () => { + it('returns only admins when there are no volunteers', async () => { await testDataSource.query(`DELETE FROM allocations`); await testDataSource.query(`DELETE FROM orders`); await testDataSource.query( @@ -154,14 +154,59 @@ describe('VolunteersService', () => { const result = await service.getVolunteersAndPantryAssignments(); - expect(result).toEqual([]); + expect(result).toEqual([ + { + id: 1, + firstName: 'John', + lastName: 'Smith', + email: 'john.smith@ssf.org', + phone: '555-010-0101', + role: 'admin', + userCognitoSub: '', + active: true, + pantryIds: [], + }, + { + id: 2, + firstName: 'Sarah', + lastName: 'Johnson', + email: 'sarah.j@ssf.org', + phone: '555-010-0102', + role: 'admin', + userCognitoSub: '', + active: true, + pantryIds: [], + }, + ]); }); - it('returns all volunteers with their pantry assignments', async () => { + it('returns all volunteers and admins with their pantry assignments', async () => { const result = await service.getVolunteersAndPantryAssignments(); - expect(result.length).toEqual(4); + expect(result.length).toEqual(6); expect(result).toEqual([ + { + id: 1, + firstName: 'John', + lastName: 'Smith', + email: 'john.smith@ssf.org', + phone: '555-010-0101', + role: 'admin', + userCognitoSub: '', + active: true, + pantryIds: [], + }, + { + id: 2, + firstName: 'Sarah', + lastName: 'Johnson', + email: 'sarah.j@ssf.org', + phone: '555-010-0102', + role: 'admin', + userCognitoSub: '', + active: true, + pantryIds: [], + }, { id: 6, firstName: 'James', diff --git a/apps/backend/src/volunteers/volunteers.service.ts b/apps/backend/src/volunteers/volunteers.service.ts index a904eca69..8fb2f297e 100644 --- a/apps/backend/src/volunteers/volunteers.service.ts +++ b/apps/backend/src/volunteers/volunteers.service.ts @@ -41,6 +41,7 @@ export class VolunteersService { async getVolunteersAndPantryAssignments(): Promise { const volunteers = await this.usersService.findUsersByRoles([ Role.VOLUNTEER, + Role.ADMIN, ]); return volunteers.map((v) => { diff --git a/apps/frontend/src/api/apiClient.ts b/apps/frontend/src/api/apiClient.ts index dbc12d98c..9225a5a31 100644 --- a/apps/frontend/src/api/apiClient.ts +++ b/apps/frontend/src/api/apiClient.ts @@ -286,6 +286,14 @@ export class ApiClient { await this.axiosInstance.patch(`/api/users/${userId}/promote-volunteer`); } + public async deactivateUser(userId: number): Promise { + await this.axiosInstance.patch(`/api/users/${userId}/deactivate`); + } + + public async reactivateUser(userId: number): Promise { + await this.axiosInstance.patch(`/api/users/${userId}/reactivate`); + } + public async getFoodRequest(requestId: number): Promise { return this.axiosInstance .get(`/api/requests/${requestId}`) diff --git a/apps/frontend/src/chakra-ui.d.ts b/apps/frontend/src/chakra-ui.d.ts index 98db62220..1a51c05d7 100644 --- a/apps/frontend/src/chakra-ui.d.ts +++ b/apps/frontend/src/chakra-ui.d.ts @@ -24,7 +24,7 @@ declare module '@chakra-ui/react' { // Menu components export interface MenuTriggerProps extends ComponentPropsStrictChildren {} - export interface MenuContentProps extends ComponentPropsStrictChildren {} + export interface MenuContentProps extends ComponentPropsLenientChildren {} export interface MenuItemProps extends ComponentPropsLenientChildren {} export interface MenuPositionerProps extends ComponentPropsStrictChildren {} export interface MenuRootProps extends ComponentPropsStrictChildren {} diff --git a/apps/frontend/src/components/Navbar.tsx b/apps/frontend/src/components/Navbar.tsx index dc3eb3101..8601ca700 100644 --- a/apps/frontend/src/components/Navbar.tsx +++ b/apps/frontend/src/components/Navbar.tsx @@ -29,10 +29,8 @@ const ROLE_NAV_SECTIONS: Record = { [Role.ADMIN]: [ { type: 'group', - label: 'Volunteers', - children: [ - { label: 'Volunteer Management', to: ROUTES.VOLUNTEER_MANAGEMENT }, - ], + label: 'Users', + children: [{ label: 'User Management', to: ROUTES.VOLUNTEER_MANAGEMENT }], }, { type: 'group', diff --git a/apps/frontend/src/components/forms/addNewVolunteerModal.tsx b/apps/frontend/src/components/forms/addNewVolunteerModal.tsx index e0dcafd34..1c0e3e96e 100644 --- a/apps/frontend/src/components/forms/addNewVolunteerModal.tsx +++ b/apps/frontend/src/components/forms/addNewVolunteerModal.tsx @@ -128,7 +128,7 @@ const NewVolunteerModal: React.FC = ({ fontFamily="Inter" color="#000" > - Add New Volunteer + Add New User setIsOpen(false)} @@ -140,7 +140,7 @@ const NewVolunteerModal: React.FC = ({ - Complete all information in the form to register a new volunteer. + Complete all information in the form to register a new user. diff --git a/apps/frontend/src/components/forms/assignVolunteersModal.tsx b/apps/frontend/src/components/forms/assignVolunteersModal.tsx index 5b1012fab..ea46af75f 100644 --- a/apps/frontend/src/components/forms/assignVolunteersModal.tsx +++ b/apps/frontend/src/components/forms/assignVolunteersModal.tsx @@ -17,6 +17,7 @@ import { AlertStatus, ApprovedPantryResponse, Assignments, + Role, } from '../../types/types'; import { SearchIcon } from 'lucide-react'; import { getInitials, USER_ICON_COLORS } from '@utils/utils'; @@ -65,11 +66,13 @@ const AssignVolunteersModal: React.FC = ({ 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 normalized: VolunteerDisplay[] = allVolunteers + .filter((v) => v.active) + .map((v) => ({ + userId: v.id, + firstName: v.firstName, + lastName: v.lastName, + })); setVolunteers(normalized); setSelectedIds(new Set(assignedIds)); diff --git a/apps/frontend/src/components/forms/confirmActionModal.tsx b/apps/frontend/src/components/forms/confirmActionModal.tsx new file mode 100644 index 000000000..2bebeb0d7 --- /dev/null +++ b/apps/frontend/src/components/forms/confirmActionModal.tsx @@ -0,0 +1,80 @@ +import { Dialog, Text, Button, CloseButton } from '@chakra-ui/react'; +import { useModalBodyCleanup } from '../../hooks/modalBodyCleanup'; + +interface ConfirmActionModalProps { + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; + volunteerName: string; + action: 'activate' | 'deactivate'; +} + +const ConfirmActionModal: React.FC = ({ + isOpen, + onClose, + onConfirm, + volunteerName, + action, +}) => { + useModalBodyCleanup(); + + return ( + !e.open && onClose()} + > + + + + + + {action === 'activate' ? 'Activate user' : 'Deactivate user'} + + + + + + + + + Are you sure you want to {action} {volunteerName}? + + + + + + + + + + + ); +}; + +export default ConfirmActionModal; diff --git a/apps/frontend/src/containers/homepage.tsx b/apps/frontend/src/containers/homepage.tsx index fca3ab8e0..0e9e931c7 100644 --- a/apps/frontend/src/containers/homepage.tsx +++ b/apps/frontend/src/containers/homepage.tsx @@ -148,7 +148,7 @@ const Homepage: React.FC = () => { - Volunteer Management + User Management diff --git a/apps/frontend/src/containers/loginPage.tsx b/apps/frontend/src/containers/loginPage.tsx index cc08a821e..a92fa73d5 100644 --- a/apps/frontend/src/containers/loginPage.tsx +++ b/apps/frontend/src/containers/loginPage.tsx @@ -75,6 +75,13 @@ const LoginPage: React.FC = () => { navigate(from, { replace: true }); return; } + if (error.message === 'User is disabled.') { + setAlertMessage( + 'Your account has been deactivated.', + AlertStatus.ERROR, + ); + return; + } if ( error.name === 'NotAuthorizedException' || error.name === 'UserNotFoundException' diff --git a/apps/frontend/src/containers/volunteerManagement.tsx b/apps/frontend/src/containers/volunteerManagement.tsx index e37d343fe..970a55227 100644 --- a/apps/frontend/src/containers/volunteerManagement.tsx +++ b/apps/frontend/src/containers/volunteerManagement.tsx @@ -8,6 +8,7 @@ import { Input, VStack, Box, + Badge, InputGroup, Pagination, ButtonGroup, @@ -22,10 +23,11 @@ import { ChevronLeft, EllipsisVertical, } from 'lucide-react'; -import { AlertStatus, User } from '../types/types'; +import { AlertStatus, Role, User } from '../types/types'; import ApiClient from '@api/apiClient'; import NewVolunteerModal from '@components/forms/addNewVolunteerModal'; import PromoteVolunteerModal from '@components/forms/promoteVolunteerModal'; +import ConfirmActionModal from '@components/forms/confirmActionModal'; import { FloatingAlert } from '@components/floatingAlert'; import { useAlert } from '../hooks/alert'; import { getInitials, USER_ICON_COLORS } from '@utils/utils'; @@ -37,6 +39,7 @@ const VolunteerManagement: React.FC = () => { const [searchName, setSearchName] = useState(''); const [selectedVolunteer, setSelectedVolunteer] = useState(null); const [isPromoteModalOpen, setIsPromoteModalOpen] = useState(false); + const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false); const [alertState, setAlertMessage] = useAlert(); @@ -77,10 +80,37 @@ const VolunteerManagement: React.FC = () => { } }; - const filteredVolunteers = volunteers.filter((a) => { - const fullName = `${a.firstName} ${a.lastName}`.toLowerCase(); - return fullName.includes(searchName.toLowerCase()); - }); + const handleToggleActive = async () => { + if (!selectedVolunteer) return; + + const wasActive = selectedVolunteer.active; + const fullName = `${selectedVolunteer.firstName} ${selectedVolunteer.lastName}`; + try { + if (wasActive) { + await ApiClient.deactivateUser(selectedVolunteer.id); + } else { + await ApiClient.reactivateUser(selectedVolunteer.id); + } + setAlertMessage( + `${fullName} has been ${wasActive ? 'deactivated' : 'activated'}.`, + AlertStatus.INFO, + ); + fetchVolunteers(); + } catch { + setAlertMessage( + `Failed to ${wasActive ? 'deactivate' : 'activate'} user.`, + AlertStatus.ERROR, + ); + } + }; + + const filteredVolunteers = volunteers + .filter((a) => { + const fullName = `${a.firstName} ${a.lastName}`.toLowerCase(); + return fullName.includes(searchName.toLowerCase()); + }) + // Deactivated users sort to the bottom of the list. + .sort((a, b) => Number(b.active) - Number(a.active)); const paginatedVolunteers = filteredVolunteers.slice( (currentPage - 1) * pageSize, @@ -96,7 +126,7 @@ const VolunteerManagement: React.FC = () => { return ( - Volunteer Management + User Management {alertState && ( { { - setAlertMessage('Volunteer added.', AlertStatus.INFO); + setAlertMessage('User added.', AlertStatus.INFO); }} onSubmitFail={() => { - setAlertMessage( - 'Volunteer could not be added.', - AlertStatus.ERROR, - ); + setAlertMessage('User could not be added.', AlertStatus.ERROR); }} /> @@ -160,7 +187,14 @@ const VolunteerManagement: React.FC = () => { textStyle="p2" fontWeight={600} > - Volunteer + Users + + + Status { { {volunteer.firstName} {volunteer.lastName} - {volunteer.email} - - + - navigate( - `${ROUTES.PANTRY_MANAGEMENT}?volunteerId=${volunteer.id}`, - ) - } + fontWeight={500} + fontSize="12px" + bg={volunteer.active ? 'teal.200' : 'neutral.300'} + color={volunteer.active ? 'teal.hover' : 'black'} > - View Assigned Pantries - + {volunteer.active ? 'Active' : 'Deactivated'} + + + {volunteer.email} + + {volunteer.role === Role.VOLUNTEER && ( + + navigate( + `${ROUTES.PANTRY_MANAGEMENT}?volunteerId=${volunteer.id}`, + ) + } + > + View Assigned Pantries + + )} @@ -235,14 +290,25 @@ const VolunteerManagement: React.FC = () => { + {volunteer.role === Role.VOLUNTEER && ( + { + setSelectedVolunteer(volunteer); + setIsPromoteModalOpen(true); + }} + > + Promote to Admin + + )} { setSelectedVolunteer(volunteer); - setIsPromoteModalOpen(true); + setIsConfirmModalOpen(true); }} > - Promote to Admin + {volunteer.active ? 'Deactivate' : 'Activate'} @@ -319,6 +385,19 @@ const VolunteerManagement: React.FC = () => { volunteerName={`${selectedVolunteer.firstName} ${selectedVolunteer.lastName}`} /> )} + + {selectedVolunteer && ( + { + setIsConfirmModalOpen(false); + setSelectedVolunteer(null); + }} + onConfirm={handleToggleActive} + volunteerName={`${selectedVolunteer.firstName} ${selectedVolunteer.lastName}`} + action={selectedVolunteer.active ? 'deactivate' : 'activate'} + /> + )} ); }; From 14c235c871339b75bd6eb4c7e46f7623f8482cae Mon Sep 17 00:00:00 2001 From: Justin Wang Date: Fri, 3 Jul 2026 18:50:37 -0400 Subject: [PATCH 2/5] filter inactive volunteers from pantry management assignee icons --- apps/backend/src/pantries/pantries.service.ts | 1 + apps/backend/src/pantries/types.ts | 1 + apps/frontend/src/containers/adminPantryManagement.tsx | 4 +++- apps/frontend/src/types/types.ts | 1 + 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/backend/src/pantries/pantries.service.ts b/apps/backend/src/pantries/pantries.service.ts index e8b42e2ea..36d341e83 100644 --- a/apps/backend/src/pantries/pantries.service.ts +++ b/apps/backend/src/pantries/pantries.service.ts @@ -531,6 +531,7 @@ export class PantriesService { lastName: volunteer.lastName, email: volunteer.email, phone: volunteer.phone, + active: volunteer.active, })), })); } diff --git a/apps/backend/src/pantries/types.ts b/apps/backend/src/pantries/types.ts index 543f6883c..e2f9d8d51 100644 --- a/apps/backend/src/pantries/types.ts +++ b/apps/backend/src/pantries/types.ts @@ -39,6 +39,7 @@ export interface AssignedVolunteer { lastName: string; email: string; phone: string; + active: boolean; } export enum RefrigeratedDonation { diff --git a/apps/frontend/src/containers/adminPantryManagement.tsx b/apps/frontend/src/containers/adminPantryManagement.tsx index ca9bcd7f6..27dc66112 100644 --- a/apps/frontend/src/containers/adminPantryManagement.tsx +++ b/apps/frontend/src/containers/adminPantryManagement.tsx @@ -313,7 +313,9 @@ const AdminPantryManagement: React.FC = () => { {pantry.volunteers && pantry.volunteers.length > 0 ? ( (() => { - const volunteers = pantry.volunteers; + const volunteers = pantry.volunteers.filter( + (volunteer) => volunteer.active, + ); const maxVisible = 3; const hasOverflow = volunteers.length > maxVisible; diff --git a/apps/frontend/src/types/types.ts b/apps/frontend/src/types/types.ts index 8f7195de6..b33389082 100644 --- a/apps/frontend/src/types/types.ts +++ b/apps/frontend/src/types/types.ts @@ -453,6 +453,7 @@ export interface AssignedVolunteer { lastName: string; email: string; phone: string; + active: boolean; } export interface CreateFoodRequestBody { From 0795ae9025fe6e1198c80fd53c59df66b33bb741 Mon Sep 17 00:00:00 2001 From: Justin Wang Date: Sat, 4 Jul 2026 11:10:32 -0400 Subject: [PATCH 3/5] comments --- .../src/pantries/pantries.controller.spec.ts | 2 ++ .../src/pantries/pantries.service.spec.ts | 23 ++++++++++++++++++- apps/frontend/src/app.tsx | 2 +- ...nteerManagement.tsx => userManagement.tsx} | 23 ++++++++++--------- 4 files changed, 37 insertions(+), 13 deletions(-) rename apps/frontend/src/containers/{volunteerManagement.tsx => userManagement.tsx} (95%) diff --git a/apps/backend/src/pantries/pantries.controller.spec.ts b/apps/backend/src/pantries/pantries.controller.spec.ts index e943c5d28..c55aa1fa3 100644 --- a/apps/backend/src/pantries/pantries.controller.spec.ts +++ b/apps/backend/src/pantries/pantries.controller.spec.ts @@ -394,6 +394,7 @@ describe('PantriesController', () => { lastName: 'Johnson', email: 'alice.johnson@example.com', phone: '(617) 555-0100', + active: true, }, { userId: 11, @@ -401,6 +402,7 @@ describe('PantriesController', () => { lastName: 'Williams', email: 'bob.williams@example.com', phone: '(617) 555-0101', + active: false, }, ], }, diff --git a/apps/backend/src/pantries/pantries.service.spec.ts b/apps/backend/src/pantries/pantries.service.spec.ts index a5387fee4..c73969c0f 100644 --- a/apps/backend/src/pantries/pantries.service.spec.ts +++ b/apps/backend/src/pantries/pantries.service.spec.ts @@ -8,7 +8,6 @@ import { ConflictException, ForbiddenException, InternalServerErrorException, - Logger, NotFoundException, } from '@nestjs/common'; import { PantryApplicationDto } from './dtos/pantry-application.dto'; @@ -943,10 +942,32 @@ describe('PantriesService', () => { expect(v.lastName).toBeDefined(); expect(v.email).toBeDefined(); expect(v.phone).toBeDefined(); + expect(typeof v.active).toBe('boolean'); }); }); }); + it('returns the active status for each volunteer', async () => { + await testDataSource.query( + `UPDATE users SET active = false WHERE email = 'james.t@volunteer.org'`, + ); + + const result = await service.getApprovedPantriesWithVolunteers(); + const volunteers = result.flatMap((p) => p.volunteers); + + const inactiveVolunteer = volunteers.find( + (v) => v.email === 'james.t@volunteer.org', + ); + expect(inactiveVolunteer).toBeDefined(); + expect(inactiveVolunteer?.active).toBe(false); + + const activeVolunteer = volunteers.find( + (v) => v.email === 'maria.g@volunteer.org', + ); + expect(activeVolunteer).toBeDefined(); + expect(activeVolunteer?.active).toBe(true); + }); + it('should return empty volunteers array when pantry has no volunteers', async () => { await service.addPantry({ contactFirstName: 'Test', diff --git a/apps/frontend/src/app.tsx b/apps/frontend/src/app.tsx index 614df457a..c7137a69f 100644 --- a/apps/frontend/src/app.tsx +++ b/apps/frontend/src/app.tsx @@ -7,7 +7,7 @@ import ApplicationSubmitted from '@containers/applicationSubmitted'; import { submitPantryApplicationForm } from '@components/forms/pantryApplicationForm'; import ApprovePantries from '@containers/approvePantries'; import PantryApplicationDetails from '@containers/pantryApplicationDetails'; -import VolunteerManagement from '@containers/volunteerManagement'; +import VolunteerManagement from '@containers/userManagement'; import AdminDonation from '@containers/adminDonation'; import Homepage from '@containers/homepage'; import AdminOrderManagement from '@containers/adminOrderManagement'; diff --git a/apps/frontend/src/containers/volunteerManagement.tsx b/apps/frontend/src/containers/userManagement.tsx similarity index 95% rename from apps/frontend/src/containers/volunteerManagement.tsx rename to apps/frontend/src/containers/userManagement.tsx index 970a55227..183ced43a 100644 --- a/apps/frontend/src/containers/volunteerManagement.tsx +++ b/apps/frontend/src/containers/userManagement.tsx @@ -290,17 +290,18 @@ const VolunteerManagement: React.FC = () => { - {volunteer.role === Role.VOLUNTEER && ( - { - setSelectedVolunteer(volunteer); - setIsPromoteModalOpen(true); - }} - > - Promote to Admin - - )} + {volunteer.role === Role.VOLUNTEER && + volunteer.active && ( + { + setSelectedVolunteer(volunteer); + setIsPromoteModalOpen(true); + }} + > + Promote to Admin + + )} { From 6b29b238574b0542a809c31765fae84c69538e47 Mon Sep 17 00:00:00 2001 From: Justin Wang Date: Sun, 5 Jul 2026 13:16:50 -0400 Subject: [PATCH 4/5] comments --- apps/backend/src/orders/order.service.ts | 4 ++++ apps/backend/src/pantries/types.ts | 1 + apps/backend/src/volunteers/types.ts | 1 + .../volunteers/volunteers.controller.spec.ts | 15 +++++++++++--- .../src/volunteers/volunteers.controller.ts | 10 +++++++--- .../src/volunteers/volunteers.service.ts | 20 +++++++++++-------- .../frontend/src/components/dashboardCard.tsx | 7 ++++++- .../forms/assignVolunteersModal.tsx | 2 +- .../src/containers/adminDashboard.tsx | 1 + .../src/containers/adminOrderManagement.tsx | 7 ++++++- .../src/containers/pantryDashboard.tsx | 1 + .../src/containers/pantryOrderManagement.tsx | 9 ++++++--- .../src/containers/volunteerDashboard.tsx | 1 + .../containers/volunteerOrderManagement.tsx | 7 ++++++- apps/frontend/src/types/types.ts | 2 ++ 15 files changed, 67 insertions(+), 21 deletions(-) diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index ad017e692..4cc7f34a3 100644 --- a/apps/backend/src/orders/order.service.ts +++ b/apps/backend/src/orders/order.service.ts @@ -71,6 +71,7 @@ export class OrdersService { 'assignee.id', 'assignee.firstName', 'assignee.lastName', + 'assignee.active', ]); if (filters?.status) { @@ -109,6 +110,7 @@ export class OrdersService { 'assignee.id', 'assignee.firstName', 'assignee.lastName', + 'assignee.active', ]) .getMany(); @@ -154,6 +156,7 @@ export class OrdersService { 'assignee.id', 'assignee.firstName', 'assignee.lastName', + 'assignee.active', ]) .where('order.assigneeId = :volunteerId', { volunteerId }) .orderBy('order.createdAt', 'DESC') @@ -636,6 +639,7 @@ ${request.pantry.shipmentAddressCity}, ${request.pantry.shipmentAddressState} ${ id: order.assignee.id, firstName: order.assignee.firstName, lastName: order.assignee.lastName, + active: order.assignee.active, }, })); } diff --git a/apps/backend/src/pantries/types.ts b/apps/backend/src/pantries/types.ts index e2f9d8d51..f671dbcd7 100644 --- a/apps/backend/src/pantries/types.ts +++ b/apps/backend/src/pantries/types.ts @@ -30,6 +30,7 @@ export interface OrderSummary { id: number; firstName: string; lastName: string; + active: boolean; }; } diff --git a/apps/backend/src/volunteers/types.ts b/apps/backend/src/volunteers/types.ts index 1f67ad033..cab684fd0 100644 --- a/apps/backend/src/volunteers/types.ts +++ b/apps/backend/src/volunteers/types.ts @@ -24,4 +24,5 @@ export type OrderAssignee = { id: number; firstName: string; lastName: string; + active: boolean; }; diff --git a/apps/backend/src/volunteers/volunteers.controller.spec.ts b/apps/backend/src/volunteers/volunteers.controller.spec.ts index 29381e36f..8178871a1 100644 --- a/apps/backend/src/volunteers/volunteers.controller.spec.ts +++ b/apps/backend/src/volunteers/volunteers.controller.spec.ts @@ -103,14 +103,18 @@ describe('VolunteersController', () => { })[], ); - const result = await controller.getAllVolunteers(); + const req: AuthenticatedRequest = { + user: { id: 99 }, + } as AuthenticatedRequest; + + const result = await controller.getAllVolunteers(req); expect(result).toEqual(expectedVolunteers); expect(result.length).toBe(2); expect(result.every((u) => u.role === Role.VOLUNTEER)).toBe(true); expect( mockVolunteersService.getVolunteersAndPantryAssignments, - ).toHaveBeenCalled(); + ).toHaveBeenCalledWith(99); }); }); @@ -179,7 +183,12 @@ describe('VolunteersController', () => { const req: AuthenticatedRequest = { user: { id: 6 }, } as AuthenticatedRequest; - const assignee = { id: 6, firstName: 'James', lastName: 'Thomas' }; + const assignee = { + id: 6, + firstName: 'James', + lastName: 'Thomas', + active: true, + }; const recentOrders: Partial[] = [ { orderId: 4, diff --git a/apps/backend/src/volunteers/volunteers.controller.ts b/apps/backend/src/volunteers/volunteers.controller.ts index 70f05e3c8..2e176eef8 100644 --- a/apps/backend/src/volunteers/volunteers.controller.ts +++ b/apps/backend/src/volunteers/volunteers.controller.ts @@ -22,15 +22,19 @@ export class VolunteersController { @Roles(Role.ADMIN) @Get('/') - async getAllVolunteers(): Promise { - return this.volunteersService.getVolunteersAndPantryAssignments(); + async getAllVolunteers( + @Req() req: AuthenticatedRequest, + ): Promise { + return this.volunteersService.getVolunteersAndPantryAssignments( + req.user.id, + ); } @CheckOwnership({ idParam: 'id', resolver: resolveVolunteerAuthorizedUserIds, }) - @Roles(Role.VOLUNTEER) + @Roles(Role.VOLUNTEER, Role.ADMIN) @Get('/:id/pantries') async getVolunteerPantries( @Param('id', ParseIntPipe) id: number, diff --git a/apps/backend/src/volunteers/volunteers.service.ts b/apps/backend/src/volunteers/volunteers.service.ts index 8fb2f297e..d2e1f836a 100644 --- a/apps/backend/src/volunteers/volunteers.service.ts +++ b/apps/backend/src/volunteers/volunteers.service.ts @@ -38,19 +38,23 @@ export class VolunteersService { return volunteer; } - async getVolunteersAndPantryAssignments(): Promise { + async getVolunteersAndPantryAssignments( + excludeUserId?: number, + ): Promise { const volunteers = await this.usersService.findUsersByRoles([ Role.VOLUNTEER, Role.ADMIN, ]); - return volunteers.map((v) => { - const { pantries, ...volunteerWithoutPantries } = v; - return { - ...volunteerWithoutPantries, - pantryIds: pantries?.map((p) => p.pantryId) || [], - }; - }); + return volunteers + .filter((v) => v.id !== excludeUserId) + .map((v) => { + const { pantries, ...volunteerWithoutPantries } = v; + return { + ...volunteerWithoutPantries, + pantryIds: pantries?.map((p) => p.pantryId) || [], + }; + }); } async getVolunteerPantries(volunteerId: number): Promise { diff --git a/apps/frontend/src/components/dashboardCard.tsx b/apps/frontend/src/components/dashboardCard.tsx index 406933496..251137601 100644 --- a/apps/frontend/src/components/dashboardCard.tsx +++ b/apps/frontend/src/components/dashboardCard.tsx @@ -197,7 +197,12 @@ const DashboardCard: React.FC = ({ w="30px" h="30px" borderRadius="full" - bg={USER_ICON_COLORS[assignee.id % USER_ICON_COLORS.length]} + bg={ + assignee.active + ? USER_ICON_COLORS[assignee.id % USER_ICON_COLORS.length] + : 'neutral.300' + } + opacity={assignee.active ? 1 : 0.6} color="white" display="flex" alignItems="center" diff --git a/apps/frontend/src/components/forms/assignVolunteersModal.tsx b/apps/frontend/src/components/forms/assignVolunteersModal.tsx index ea46af75f..e4a4dffe4 100644 --- a/apps/frontend/src/components/forms/assignVolunteersModal.tsx +++ b/apps/frontend/src/components/forms/assignVolunteersModal.tsx @@ -67,7 +67,7 @@ const AssignVolunteersModal: React.FC = ({ const assignedIds = new Set(pantry.volunteers.map((v) => v.userId)); const normalized: VolunteerDisplay[] = allVolunteers - .filter((v) => v.active) + .filter((v) => v.active && v.role === Role.VOLUNTEER) .map((v) => ({ userId: v.id, firstName: v.firstName, diff --git a/apps/frontend/src/containers/adminDashboard.tsx b/apps/frontend/src/containers/adminDashboard.tsx index 24a148a86..eca2e7e8e 100644 --- a/apps/frontend/src/containers/adminDashboard.tsx +++ b/apps/frontend/src/containers/adminDashboard.tsx @@ -218,6 +218,7 @@ const AdminDashboard: React.FC = () => { id: order.assignee.id, firstName: order.assignee.firstName, lastName: order.assignee.lastName, + active: order.assignee.active, }} onLinkClick={() => navigate(`/admin-order-management?orderId=${order.orderId}`) diff --git a/apps/frontend/src/containers/adminOrderManagement.tsx b/apps/frontend/src/containers/adminOrderManagement.tsx index dc293f146..d267ae51e 100644 --- a/apps/frontend/src/containers/adminOrderManagement.tsx +++ b/apps/frontend/src/containers/adminOrderManagement.tsx @@ -769,7 +769,12 @@ const OrderStatusSection: React.FC = ({ { id: order.assignee.id, firstName: order.assignee.firstName, lastName: order.assignee.lastName, + active: order.assignee.active, }} onLinkClick={() => navigate( diff --git a/apps/frontend/src/containers/pantryOrderManagement.tsx b/apps/frontend/src/containers/pantryOrderManagement.tsx index d4b66c447..b4b5532db 100644 --- a/apps/frontend/src/containers/pantryOrderManagement.tsx +++ b/apps/frontend/src/containers/pantryOrderManagement.tsx @@ -552,10 +552,13 @@ const OrderStatusSection: React.FC = ({ { id: order.assignee.id, firstName: order.assignee.firstName, lastName: order.assignee.lastName, + active: order.assignee.active, }} onLinkClick={() => navigate( diff --git a/apps/frontend/src/containers/volunteerOrderManagement.tsx b/apps/frontend/src/containers/volunteerOrderManagement.tsx index bcc1acbbe..868ff6d39 100644 --- a/apps/frontend/src/containers/volunteerOrderManagement.tsx +++ b/apps/frontend/src/containers/volunteerOrderManagement.tsx @@ -842,7 +842,12 @@ const OrderStatusSection: React.FC = ({ > Date: Mon, 6 Jul 2026 11:25:22 -0400 Subject: [PATCH 5/5] comment --- apps/frontend/src/containers/adminPantryManagement.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/frontend/src/containers/adminPantryManagement.tsx b/apps/frontend/src/containers/adminPantryManagement.tsx index 27dc66112..a6baea579 100644 --- a/apps/frontend/src/containers/adminPantryManagement.tsx +++ b/apps/frontend/src/containers/adminPantryManagement.tsx @@ -333,10 +333,14 @@ const AdminPantryManagement: React.FC = () => { key={volunteer.userId} borderRadius="full" bg={ - USER_ICON_COLORS[ - volunteer.userId % USER_ICON_COLORS.length - ] + volunteer.active + ? USER_ICON_COLORS[ + volunteer.userId % + USER_ICON_COLORS.length + ] + : 'neutral.300' } + opacity={volunteer.active ? 1 : 0.6} width="33px" height="33px" display="flex"