Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/backend/src/orders/order.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export class OrdersService {
'assignee.id',
'assignee.firstName',
'assignee.lastName',
'assignee.active',
]);

if (filters?.status) {
Expand Down Expand Up @@ -109,6 +110,7 @@ export class OrdersService {
'assignee.id',
'assignee.firstName',
'assignee.lastName',
'assignee.active',
])
.where('order.assigneeId = :volunteerId', { volunteerId })
.getMany();
Expand Down Expand Up @@ -150,6 +152,7 @@ export class OrdersService {
'assignee.id',
'assignee.firstName',
'assignee.lastName',
'assignee.active',
])
.where('order.assigneeId = :volunteerId', { volunteerId })
.orderBy('order.createdAt', 'DESC')
Expand Down Expand Up @@ -655,6 +658,7 @@ ${request.pantry.shipmentAddressCity}, ${request.pantry.shipmentAddressState} ${
id: order.assignee.id,
firstName: order.assignee.firstName,
lastName: order.assignee.lastName,
active: order.assignee.active,
},
}));
}
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/src/pantries/pantries.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,13 +394,15 @@ describe('PantriesController', () => {
lastName: 'Johnson',
email: 'alice.johnson@example.com',
phone: '(617) 555-0100',
active: true,
},
{
userId: 11,
firstName: 'Bob',
lastName: 'Williams',
email: 'bob.williams@example.com',
phone: '(617) 555-0101',
active: false,
},
],
},
Expand Down
23 changes: 22 additions & 1 deletion apps/backend/src/pantries/pantries.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
ConflictException,
ForbiddenException,
InternalServerErrorException,
Logger,
NotFoundException,
} from '@nestjs/common';
import { PantryApplicationDto } from './dtos/pantry-application.dto';
Expand Down Expand Up @@ -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',
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/pantries/pantries.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@ export class PantriesService {
lastName: volunteer.lastName,
email: volunteer.email,
phone: volunteer.phone,
active: volunteer.active,
Comment thread
Juwang110 marked this conversation as resolved.
})),
}));
}
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/src/pantries/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface OrderSummary {
id: number;
firstName: string;
lastName: string;
active: boolean;
};
}

Expand All @@ -39,6 +40,7 @@ export interface AssignedVolunteer {
lastName: string;
email: string;
phone: string;
active: boolean;
}

export enum RefrigeratedDonation {
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/volunteers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ export type OrderAssignee = {
id: number;
firstName: string;
lastName: string;
active: boolean;
};
15 changes: 12 additions & 3 deletions apps/backend/src/volunteers/volunteers.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});

Expand Down Expand Up @@ -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<VolunteerOrder>[] = [
{
orderId: 4,
Expand Down
10 changes: 7 additions & 3 deletions apps/backend/src/volunteers/volunteers.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,19 @@ export class VolunteersController {

@Roles(Role.ADMIN)
@Get('/')
async getAllVolunteers(): Promise<Assignments[]> {
return this.volunteersService.getVolunteersAndPantryAssignments();
async getAllVolunteers(
@Req() req: AuthenticatedRequest,
): Promise<Assignments[]> {
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,
Expand Down
53 changes: 49 additions & 4 deletions apps/backend/src/volunteers/volunteers.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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',
Expand Down
21 changes: 13 additions & 8 deletions apps/backend/src/volunteers/volunteers.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,23 @@ export class VolunteersService {
return volunteer;
}

async getVolunteersAndPantryAssignments(): Promise<Assignments[]> {
async getVolunteersAndPantryAssignments(
excludeUserId?: number,
): Promise<Assignments[]> {
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<Pantry[]> {
Expand Down
8 changes: 8 additions & 0 deletions apps/frontend/src/api/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,14 @@ export class ApiClient {
await this.axiosInstance.patch(`/api/users/${userId}/promote-volunteer`);
}

public async deactivateUser(userId: number): Promise<void> {
await this.axiosInstance.patch(`/api/users/${userId}/deactivate`);
}

public async reactivateUser(userId: number): Promise<void> {
await this.axiosInstance.patch(`/api/users/${userId}/reactivate`);
}

public async getFoodRequest(requestId: number): Promise<FoodRequest> {
return this.axiosInstance
.get(`/api/requests/${requestId}`)
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/src/chakra-ui.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand Down
6 changes: 2 additions & 4 deletions apps/frontend/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,8 @@ const ROLE_NAV_SECTIONS: Record<Role, NavSection[]> = {
[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',
Expand Down
7 changes: 6 additions & 1 deletion apps/frontend/src/components/dashboardCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,12 @@ const DashboardCard: React.FC<DashboardCardProps> = ({
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"
Expand Down
4 changes: 2 additions & 2 deletions apps/frontend/src/components/forms/addNewVolunteerModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ const NewVolunteerModal: React.FC<NewVolunteerModalProps> = ({
fontFamily="Inter"
color="#000"
>
Add New Volunteer
Add New User
</Dialog.Title>
<CloseButton
onClick={() => setIsOpen(false)}
Expand All @@ -140,7 +140,7 @@ const NewVolunteerModal: React.FC<NewVolunteerModalProps> = ({
</Dialog.Header>
<Dialog.Body color="neutral.800" fontWeight={600} textStyle="p2">
<Text mb="1.5em" color="#52525B" fontWeight={400}>
Complete all information in the form to register a new volunteer.
Complete all information in the form to register a new user.
</Text>
<Flex gap={8} justifyContent="flex-start" my={4}>
<Field.Root>
Expand Down
13 changes: 8 additions & 5 deletions apps/frontend/src/components/forms/assignVolunteersModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -65,11 +66,13 @@ const AssignVolunteersModal: React.FC<AssignVolunteersModalProps> = ({

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 && v.role === Role.VOLUNTEER)
.map((v) => ({
userId: v.id,
firstName: v.firstName,
lastName: v.lastName,
}));

setVolunteers(normalized);
setSelectedIds(new Set(assignedIds));
Expand Down
Loading
Loading