diff --git a/apps/backend/src/orders/order.controller.ts b/apps/backend/src/orders/order.controller.ts
index f53f25a5d..11afca8ae 100644
--- a/apps/backend/src/orders/order.controller.ts
+++ b/apps/backend/src/orders/order.controller.ts
@@ -46,6 +46,7 @@ export class OrdersController {
// Called like: /?status=pending&pantryName=Test%20Pantry&pantryName=Test%20Pantry%202
// %20 is the URL encoded space character
// This gets all orders where the status is pending and the pantry name is either Test Pantry or Test Pantry 2
+ @Roles(Role.ADMIN)
@Get('/')
async getAllOrders(
@Query('status') status?: string,
diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts
index ccc9ebe5c..f8b177f4b 100644
--- a/apps/backend/src/orders/order.service.ts
+++ b/apps/backend/src/orders/order.service.ts
@@ -117,6 +117,7 @@ export class OrdersService {
createdAt: o.createdAt,
shippedAt: o.shippedAt,
deliveredAt: o.deliveredAt,
+ pantryId: o.request.pantryId,
pantryName: o.request.pantry.pantryName,
assignee: o.assignee,
actionCompletion,
@@ -157,6 +158,7 @@ export class OrdersService {
createdAt: o.createdAt,
shippedAt: o.shippedAt,
deliveredAt: o.deliveredAt,
+ pantryId: o.request.pantryId,
pantryName: o.request.pantry.pantryName,
assignee: o.assignee,
}));
diff --git a/apps/backend/src/volunteers/types.ts b/apps/backend/src/volunteers/types.ts
index 4c61654fb..1f67ad033 100644
--- a/apps/backend/src/volunteers/types.ts
+++ b/apps/backend/src/volunteers/types.ts
@@ -9,6 +9,7 @@ export type VolunteerOrder = {
createdAt: Date;
shippedAt: Date | null;
deliveredAt: Date | null;
+ pantryId: number;
pantryName: string;
assignee: OrderAssignee;
actionCompletion?: VolunteerActionCompletion;
diff --git a/apps/backend/src/volunteers/volunteers.controller.ts b/apps/backend/src/volunteers/volunteers.controller.ts
index a04bd01ad..8aad71695 100644
--- a/apps/backend/src/volunteers/volunteers.controller.ts
+++ b/apps/backend/src/volunteers/volunteers.controller.ts
@@ -3,6 +3,7 @@ import { Pantry } from '../pantries/pantries.entity';
import { VolunteersService } from './volunteers.service';
import { Role } from '../users/types';
import { Roles } from '../auth/roles.decorator';
+import { CheckOwnership } from '../auth/ownership.decorator';
import { Assignments, VolunteerOrder } from './types';
import { AuthenticatedRequest } from '../auth/authenticated-request';
import { OrdersService } from '../orders/order.service';
@@ -49,6 +50,10 @@ export class VolunteersController {
// returns all orders globally
// only includes actionCompletion for orders assigned to the requesting volunteer
+ @CheckOwnership({
+ idParam: 'id',
+ resolver: async ({ entityId }) => [entityId],
+ })
@Roles(Role.VOLUNTEER)
@Get('/:id/orders')
async getVolunteerOrders(
diff --git a/apps/frontend/src/containers/adminOrderManagement.tsx b/apps/frontend/src/containers/adminOrderManagement.tsx
index 26f56e931..f2939ffa7 100644
--- a/apps/frontend/src/containers/adminOrderManagement.tsx
+++ b/apps/frontend/src/containers/adminOrderManagement.tsx
@@ -157,7 +157,7 @@ const AdminOrderManagement: React.FC = () => {
// Wait until orders are loaded
const allOrders = Object.values(statusOrders).flat();
- if (allOrders.length === 0) return;
+ if (!orderIdFromUrl || allOrders.length === 0) return;
const id = Number(orderIdFromUrl);
const matchedOrder = allOrders.find((order) => order.orderId === id);
@@ -183,6 +183,39 @@ const AdminOrderManagement: React.FC = () => {
}
}, [searchParams, statusOrders, navigate]);
+ // Pre-fill pantry filter from url param and then clear the param.
+ useEffect(() => {
+ const pantryIdFromUrl = searchParams.get('pantryId');
+
+ const allOrders = Object.values(statusOrders).flat();
+ if (!pantryIdFromUrl || allOrders.length === 0) return;
+
+ const matchedOrder = allOrders.find(
+ (order) => order.request.pantryId === Number(pantryIdFromUrl),
+ );
+ const pantryName = matchedOrder?.request.pantry.pantryName;
+
+ if (pantryName) {
+ setFilterStates((prev) => ({
+ [OrderStatus.SHIPPED]: {
+ ...prev[OrderStatus.SHIPPED],
+ selectedPantries: [pantryName],
+ },
+ [OrderStatus.PENDING]: {
+ ...prev[OrderStatus.PENDING],
+ selectedPantries: [pantryName],
+ },
+ [OrderStatus.DELIVERED]: {
+ ...prev[OrderStatus.DELIVERED],
+ selectedPantries: [pantryName],
+ },
+ }));
+ } else {
+ setAlertMessage('Selected pantry has no orders');
+ navigate(ROUTES.ADMIN_ORDER_MANAGEMENT, { replace: true });
+ }
+ }, [searchParams, statusOrders, navigate, setAlertMessage]);
+
return (
@@ -362,7 +395,7 @@ const OrderStatusSection: React.FC = ({
- {orders.length === 0 ? (
+ {orders.length === 0 && filterState.selectedPantries.length === 0 ? (
= ({
)}
-
-
-
-
- Order #
-
-
- Status
-
-
- Assignee
-
-
- Pantry
-
-
- Dates
-
-
- Action Required
-
-
-
-
- {orders.map((order, index) => {
- const pantry = order.request.pantry;
-
- return (
-
-
+
+
+
+
+ No Orders
+
+
+ You have no {ORDER_STATUS_LABELS[status].toLowerCase()} orders
+ at this time.
+
+
+ ) : (
+ <>
+
+
+
+
- onOrderSelect(order.orderId)}
- >
- {order.orderId}
-
-
-
+
-
- {ORDER_STATUS_LABELS[order.status]}
-
-
-
+
-
-
- {getInitials(
- order.assignee.firstName,
- order.assignee.lastName,
- )}
-
-
-
-
+
- {pantry.pantryName}
-
-
+
- {formatDate(order.createdAt)}-
- {order.deliveredAt && formatDate(order.deliveredAt)}
-
-
+ Dates
+
+
+ Action Required
+
- );
- })}
-
-
-
- {totalPages > 1 && (
-
- onPageChange(e.page)}
- >
-
-
+
+ {orders.map((order, index) => {
+ const pantry = order.request.pantry;
+
+ return (
+
+
+ onOrderSelect(order.orderId)}
+ >
+ {order.orderId}
+
+
+
+
+ {ORDER_STATUS_LABELS[order.status]}
+
+
+
+
+
+ {getInitials(
+ order.assignee.firstName,
+ order.assignee.lastName,
+ )}
+
+
+
+
+ {pantry.pantryName}
+
+
+ {formatDate(order.createdAt)}-
+ {order.deliveredAt && formatDate(order.deliveredAt)}
+
+
+
+ );
+ })}
+
+
+
+ {totalPages > 1 && (
+
+ onPageChange(e.page)}
>
-
-
-
- (
-
+
- {page.value}
-
- )}
- />
+
+
+
+ (
+
+ {page.value}
+
+ )}
+ />
-
-
-
-
-
-
+
+
+
+
+
+
+ )}
+ >
)}
>
)}
diff --git a/apps/frontend/src/containers/adminPantryManagement.tsx b/apps/frontend/src/containers/adminPantryManagement.tsx
index e807b5ce4..f5ff8b547 100644
--- a/apps/frontend/src/containers/adminPantryManagement.tsx
+++ b/apps/frontend/src/containers/adminPantryManagement.tsx
@@ -22,11 +22,12 @@ import { useAlert } from '../hooks/alert';
import { getInitials, USER_ICON_COLORS } from '@utils/utils';
import { RefrigeratedDonation } from '../types/pantryEnums';
import AssignVolunteersModal from '@components/forms/assignVolunteersModal';
-import { useNavigate } from 'react-router-dom';
+import { useNavigate, useSearchParams } from 'react-router-dom';
import { ROUTES } from '../routes';
const AdminPantryManagement: React.FC = () => {
const navigate = useNavigate();
+ const [searchParams] = useSearchParams();
const [currentPage, setCurrentPage] = useState(1);
const [pantries, setPantries] = useState([]);
@@ -61,6 +62,40 @@ const AdminPantryManagement: React.FC = () => {
fetchPantries();
}, [setAlertMessage]);
+ // Pre-fill pantry filter from the volunteerId url param. The param is kept on
+ // success so the filter is reapplied on reload/back/refresh, and only cleared
+ // when the volunteer has no pantries or the fetch fails.
+ useEffect(() => {
+ const volunteerIdFromUrl = searchParams.get('volunteerId');
+ if (!volunteerIdFromUrl) return;
+
+ let cancelled = false;
+ (async () => {
+ try {
+ const assignedPantries = await ApiClient.getVolunteerPantries(
+ Number(volunteerIdFromUrl),
+ );
+ if (cancelled) return;
+ if (assignedPantries.length === 0) {
+ setIsAlertSuccess(false);
+ setAlertMessage('This volunteer has no assigned pantries.');
+ navigate(ROUTES.PANTRY_MANAGEMENT, { replace: true });
+ return;
+ }
+ setSelectedPantries(assignedPantries.map((p) => p.pantryName));
+ } catch {
+ if (cancelled) return;
+ setIsAlertSuccess(false);
+ setAlertMessage('Error fetching volunteer pantries');
+ navigate(ROUTES.PANTRY_MANAGEMENT, { replace: true });
+ }
+ })();
+
+ return () => {
+ cancelled = true;
+ };
+ }, [searchParams, navigate, setAlertMessage]);
+
const handleAssignVolunteersSuccess = () => {
setIsAlertSuccess(true);
setAlertMessage('Successfully assigned volunteers');
@@ -357,14 +392,14 @@ const AdminPantryManagement: React.FC = () => {
fontWeight={500}
fontSize="12px"
bgColor={
- pantry.refrigeratedDonation === RefrigeratedDonation.YES
- ? 'neutral.100'
- : 'neutral.200'
+ pantry.refrigeratedDonation === RefrigeratedDonation.NO
+ ? 'neutral.200'
+ : 'neutral.100'
}
>
- {pantry.refrigeratedDonation === RefrigeratedDonation.YES
- ? 'Refrigerator-Friendly'
- : 'Not Refrigerator-Friendly'}
+ {pantry.refrigeratedDonation === RefrigeratedDonation.NO
+ ? 'Not Refrigerator-Friendly'
+ : 'Refrigerator-Friendly'}
@@ -374,7 +409,12 @@ const AdminPantryManagement: React.FC = () => {
textStyle="p2"
variant="underline"
textDecorationColor="neutral.700"
- // TODO href or some functionality to view orders
+ cursor="pointer"
+ onClick={() =>
+ navigate(
+ `${ROUTES.ADMIN_ORDER_MANAGEMENT}?pantryId=${pantry.pantryId}`,
+ )
+ }
>
View Orders
diff --git a/apps/frontend/src/containers/volunteerAssignedPantries.tsx b/apps/frontend/src/containers/volunteerAssignedPantries.tsx
index 53e37450a..27ec4da93 100644
--- a/apps/frontend/src/containers/volunteerAssignedPantries.tsx
+++ b/apps/frontend/src/containers/volunteerAssignedPantries.tsx
@@ -362,7 +362,11 @@ const AssignedPantries: React.FC = () => {
>
{
color="neutral.700"
textStyle="p2"
onClick={() =>
- navigate(ROUTES.VOLUNTEER_REQUEST_MANAGEMENT)
+ navigate(
+ `${ROUTES.VOLUNTEER_ORDER_MANAGEMENT}?pantryId=${pantry.pantryId}`,
+ )
}
p={0}
height="auto"
diff --git a/apps/frontend/src/containers/volunteerManagement.tsx b/apps/frontend/src/containers/volunteerManagement.tsx
index b0a2cb681..efe5caafa 100644
--- a/apps/frontend/src/containers/volunteerManagement.tsx
+++ b/apps/frontend/src/containers/volunteerManagement.tsx
@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
import { ROUTES } from '../routes';
import {
Table,
@@ -22,6 +23,7 @@ import { useAlert } from '../hooks/alert';
import { getInitials, USER_ICON_COLORS } from '@utils/utils';
const VolunteerManagement: React.FC = () => {
+ const navigate = useNavigate();
const [currentPage, setCurrentPage] = useState(1);
const [volunteers, setVolunteers] = useState([]);
const [searchName, setSearchName] = useState('');
@@ -186,7 +188,12 @@ const VolunteerManagement: React.FC = () => {
textStyle="p2"
variant="underline"
textDecorationColor="neutral.700"
- href={`${ROUTES.PANTRY_MANAGEMENT}/${volunteer.id}`}
+ cursor="pointer"
+ onClick={() =>
+ navigate(
+ `${ROUTES.PANTRY_MANAGEMENT}?volunteerId=${volunteer.id}`,
+ )
+ }
>
View Assigned Pantries
diff --git a/apps/frontend/src/containers/volunteerOrderManagement.tsx b/apps/frontend/src/containers/volunteerOrderManagement.tsx
index d23677005..432eed92b 100644
--- a/apps/frontend/src/containers/volunteerOrderManagement.tsx
+++ b/apps/frontend/src/containers/volunteerOrderManagement.tsx
@@ -196,6 +196,39 @@ const VolunteerOrderManagement: React.FC = () => {
}
}, [searchParams, statusOrders, navigate]);
+ // Pre-fill pantry filter from url param and then clear the param.
+ useEffect(() => {
+ const pantryIdFromUrl = searchParams.get('pantryId');
+
+ const allOrders = Object.values(statusOrders).flat();
+ if (!pantryIdFromUrl || allOrders.length === 0) return;
+
+ const matchedOrder = allOrders.find(
+ (o) => o.pantryId === Number(pantryIdFromUrl),
+ );
+ const pantryName = matchedOrder?.pantryName;
+
+ if (pantryName) {
+ setFilterStates((prev) => ({
+ [OrderStatus.SHIPPED]: {
+ ...prev[OrderStatus.SHIPPED],
+ selectedPantries: [pantryName],
+ },
+ [OrderStatus.PENDING]: {
+ ...prev[OrderStatus.PENDING],
+ selectedPantries: [pantryName],
+ },
+ [OrderStatus.DELIVERED]: {
+ ...prev[OrderStatus.DELIVERED],
+ selectedPantries: [pantryName],
+ },
+ }));
+ } else {
+ setAlertMessage('Selected pantry has no orders');
+ navigate(ROUTES.VOLUNTEER_ORDER_MANAGEMENT, { replace: true });
+ }
+ }, [searchParams, statusOrders, navigate, setAlertMessage]);
+
const resetPageForStatus = (status: OrderStatus) => {
setCurrentPages((prev) => ({ ...prev, [status]: 1 }));
};
@@ -433,7 +466,7 @@ const OrderStatusSection: React.FC = ({
- {orders.length === 0 ? (
+ {orders.length === 0 && filterState.selectedPantries.length === 0 ? (
= ({
)}
-
-
-
-
- Order #
-
-
- Status
-
-
- Assignee
-
-
- Pantry
-
-
- Dates
-
-
- Action Required
-
-
-
-
- {orders.map((order, index) => {
- const needsAction = hasRequiredActions(order);
-
- return (
-
-
+
+
+
+
+ No Orders
+
+
+ You have no {ORDER_STATUS_LABELS[status].toLowerCase()} orders
+ at this time.
+
+
+ ) : (
+ <>
+
+
+
+
- onOrderSelect(order.orderId)}
- >
- {order.orderId}
-
-
-
+
-
- {ORDER_STATUS_LABELS[status]}
-
-
-
+
-
-
- {getInitials(
- order.assignee.firstName,
- order.assignee.lastName,
- )}
-
-
-
-
+
- {order.pantryName}
-
-
+
- {`${formatDate(String(order.createdAt))}-`}
- {order.deliveredAt &&
- formatDate(String(order.deliveredAt))}
-
-
+
- {order.assignee?.id === currentUser?.id &&
- (needsAction ? (
+ Action Required
+
+
+
+
+ {orders.map((order, index) => {
+ const needsAction = hasRequiredActions(order);
+
+ return (
+
+
onOpenActionModal(order)}
+ onClick={() => onOrderSelect(order.orderId)}
>
- Complete Required Actions
+ {order.orderId}
- ) : (
- 'No Action Required'
- ))}
-
-
- );
- })}
-
-
-
- {totalPages > 1 && (
-
- onPageChange(e.page)}
- >
-
-
+
+
+ {ORDER_STATUS_LABELS[status]}
+
+
+
+
+
+ {getInitials(
+ order.assignee.firstName,
+ order.assignee.lastName,
+ )}
+
+
+
+
+ {order.pantryName}
+
+
+ {`${formatDate(String(order.createdAt))}-`}
+ {order.deliveredAt &&
+ formatDate(String(order.deliveredAt))}
+
+
+ {order.assignee?.id === currentUser?.id &&
+ (needsAction ? (
+ onOpenActionModal(order)}
+ >
+ Complete Required Actions
+
+ ) : (
+ 'No Action Required'
+ ))}
+
+
+ );
+ })}
+
+
+
+ {totalPages > 1 && (
+
+ onPageChange(e.page)}
>
-
-
-
- (
-
+
- {page.value}
-
- )}
- />
+
+
+
+ (
+
+ {page.value}
+
+ )}
+ />
-
-
-
-
-
-
+
+
+
+
+
+
+ )}
+ >
)}
>
)}
diff --git a/apps/frontend/src/types/types.ts b/apps/frontend/src/types/types.ts
index 8addbea64..d42b0b539 100644
--- a/apps/frontend/src/types/types.ts
+++ b/apps/frontend/src/types/types.ts
@@ -362,6 +362,7 @@ export type VolunteerOrder = {
createdAt: string;
shippedAt: string | null;
deliveredAt: string | null;
+ pantryId: number;
pantryName: string;
assignee: OrderAssignee;
actionCompletion?: VolunteerActionCompletion;