diff --git a/apps/backend/src/orders/order.controller.ts b/apps/backend/src/orders/order.controller.ts index b43b8501..870dc1ef 100644 --- a/apps/backend/src/orders/order.controller.ts +++ b/apps/backend/src/orders/order.controller.ts @@ -13,7 +13,6 @@ import { Order } from './order.entity'; import { Pantry } from '../pantries/pantries.entity'; import { FoodManufacturer } from '../foodManufacturers/manufacturer.entity'; import { FoodRequest } from '../foodRequests/request.entity'; -import { Donation } from '../donations/donations.entity'; import { AllocationsService } from '../allocations/allocations.service'; import { OrderStatus } from './types'; @@ -24,8 +23,9 @@ export class OrdersController { private readonly allocationsService: AllocationsService, ) {} - // Called like: /?status=pending&pantryName=Test%20Pantry&pantryName=Test%20Pantry%2 + // 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 @Get('/') async getAllOrders( @Query('status') status?: string, @@ -47,21 +47,21 @@ export class OrdersController { return this.ordersService.getPastOrders(); } - @Get(':orderId/pantry') + @Get('/:orderId/pantry') async getPantryFromOrder( @Param('orderId', ParseIntPipe) orderId: number, ): Promise { return this.ordersService.findOrderPantry(orderId); } - @Get(':orderId/request') + @Get('/:orderId/request') async getRequestFromOrder( @Param('orderId', ParseIntPipe) orderId: number, ): Promise { return this.ordersService.findOrderFoodRequest(orderId); } - @Get(':orderId/manufacturer') + @Get('/:orderId/manufacturer') async getManufacturerFromOrder( @Param('orderId', ParseIntPipe) orderId: number, ): Promise { @@ -82,7 +82,7 @@ export class OrdersController { return this.ordersService.findOrderByRequest(requestId); } - @Get(':orderId/allocations') + @Get('/:orderId/allocations') async getAllAllocationsByOrder( @Param('orderId', ParseIntPipe) orderId: number, ) { diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index 8a36819f..05f37bca 100644 --- a/apps/backend/src/orders/order.service.ts +++ b/apps/backend/src/orders/order.service.ts @@ -16,6 +16,7 @@ export class OrdersService { const qb = this.repo .createQueryBuilder('order') .leftJoinAndSelect('order.pantry', 'pantry') + .leftJoinAndSelect('pantry.volunteers', 'volunteers') .select([ 'order.orderId', 'order.status', @@ -23,6 +24,9 @@ export class OrdersService { 'order.shippedAt', 'order.deliveredAt', 'pantry.pantryName', + 'volunteers.id', + 'volunteers.firstName', + 'volunteers.lastName', ]); if (filters?.status) { diff --git a/apps/frontend/src/api/apiClient.ts b/apps/frontend/src/api/apiClient.ts index f62efd8f..e20724bb 100644 --- a/apps/frontend/src/api/apiClient.ts +++ b/apps/frontend/src/api/apiClient.ts @@ -10,6 +10,7 @@ import { CreateFoodRequestBody, Pantry, PantryApplicationDto, + OrderSummary, UserDto, } from 'types/types'; @@ -171,7 +172,7 @@ export class ApiClient { .then((response) => response.data); } - public async getAllOrders(): Promise { + public async getAllOrders(): Promise { return this.axiosInstance .get('/api/orders/') .then((response) => response.data); diff --git a/apps/frontend/src/app.tsx b/apps/frontend/src/app.tsx index 90be09d1..c39631b4 100644 --- a/apps/frontend/src/app.tsx +++ b/apps/frontend/src/app.tsx @@ -23,6 +23,7 @@ import DonationManagement from '@containers/donationManagement'; import AdminDonation from '@containers/adminDonation'; import { pantryIdLoader } from '@loaders/pantryIdLoader'; import Homepage from '@containers/homepage'; +import AdminOrderManagement from '@containers/adminOrderManagement'; const router = createBrowserRouter([ { @@ -97,6 +98,10 @@ const router = createBrowserRouter([ path: '/admin-donation', element: , }, + { + path: '/admin-order-management', + element: , + }, { path: '/volunteer-management', element: , diff --git a/apps/frontend/src/components/forms/addNewVolunteerModal.tsx b/apps/frontend/src/components/forms/addNewVolunteerModal.tsx index 7c2fa99c..545122da 100644 --- a/apps/frontend/src/components/forms/addNewVolunteerModal.tsx +++ b/apps/frontend/src/components/forms/addNewVolunteerModal.tsx @@ -1,49 +1,52 @@ import { - Dialog, - Button, - Text, - Flex, - Field, - Input, - CloseButton, - Box + Dialog, + Button, + Text, + Flex, + Field, + Input, + CloseButton, + Box, } from '@chakra-ui/react'; import { useState } from 'react'; -import { Role, UserDto } from "../../types/types"; +import { Role, UserDto } from '../../types/types'; import ApiClient from '@api/apiClient'; import { USPhoneInput } from './usPhoneInput'; import { PlusIcon } from 'lucide-react'; interface NewVolunteerModalProps { - onSubmitSuccess?: () => void; - onSubmitFail?: () => void; + onSubmitSuccess?: () => void; + onSubmitFail?: () => void; } -const NewVolunteerModal: React.FC = ({ onSubmitSuccess, onSubmitFail }) => { - const [firstName, setFirstName] = useState(""); - const [lastName, setLastName] = useState(""); - const [email, setEmail] = useState(""); - const [phone, setPhone] = useState(""); +const NewVolunteerModal: React.FC = ({ + onSubmitSuccess, + onSubmitFail, +}) => { + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + const [email, setEmail] = useState(''); + const [phone, setPhone] = useState(''); const [isOpen, setIsOpen] = useState(false); - const [error, setError] = useState(""); + const [error, setError] = useState(''); const handleSubmit = async () => { - console.log("RAW phone value:", phone); - if (!firstName || !lastName || !email || !phone || phone === "+1") { - setError("Please fill in all fields. *"); + console.log('RAW phone value:', phone); + if (!firstName || !lastName || !email || !phone || phone === '+1') { + setError('Please fill in all fields. *'); return; } - setError(""); + setError(''); const newVolunteer: UserDto = { firstName, lastName, email, phone, - role: Role.VOLUNTEER + role: Role.VOLUNTEER, }; try { @@ -51,47 +54,63 @@ const NewVolunteerModal: React.FC = ({ onSubmitSuccess, if (onSubmitSuccess) onSubmitSuccess(); handleClear(); } catch (error: unknown) { + let hasEmailError = false; + let hasPhoneError = false; - let hasEmailError = false - let hasPhoneError = false - - if (typeof error === "object" && error !== null) { - const e = error as { response?: { data?: { message?: string | string[] } } }; + if (typeof error === 'object' && error !== null) { + const e = error as { + response?: { data?: { message?: string | string[] } }; + }; const message = e.response?.data?.message; hasEmailError = Array.isArray(message) && - message.some((msg) => typeof msg === "string" && msg.toLowerCase().includes("email")); + message.some( + (msg) => + typeof msg === 'string' && msg.toLowerCase().includes('email'), + ); hasPhoneError = Array.isArray(message) && - message.some((msg) => typeof msg === "string" && msg.toLowerCase().includes("phone")); + message.some( + (msg) => + typeof msg === 'string' && msg.toLowerCase().includes('phone'), + ); } if (hasEmailError) { - setError("Please specify a valid email. *") + setError('Please specify a valid email. *'); } else if (hasPhoneError) { - setError("Please specify a valid phone number. *") + setError('Please specify a valid phone number. *'); } else { if (onSubmitFail) onSubmitFail(); handleClear(); } } - } + }; const handleClear = () => { - setFirstName(""); - setLastName(""); - setEmail(""); - setPhone(""); - setError(""); + setFirstName(''); + setLastName(''); + setEmail(''); + setPhone(''); + setError(''); setIsOpen(false); }; return ( - @@ -100,10 +119,21 @@ const NewVolunteerModal: React.FC = ({ onSubmitSuccess, - + Add New Volunteer - setIsOpen(false)} size="md" position="absolute" top={3} right={3}/> + setIsOpen(false)} + size="md" + position="absolute" + top={3} + right={3} + /> @@ -111,20 +141,54 @@ const NewVolunteerModal: React.FC = ({ onSubmitSuccess, - First Name - setFirstName(e.target.value)}/> + + First Name + + setFirstName(e.target.value)} + /> - Last Name - setLastName(e.target.value)}/> + + Last Name + + setLastName(e.target.value)} + /> - Email - setEmail(e.target.value)}/> + + Email + + setEmail(e.target.value)} + /> - Phone Number + + Phone Number + = ({ onSubmitSuccess, /> {error && ( - + {error} )} - - + + @@ -151,9 +237,4 @@ const NewVolunteerModal: React.FC = ({ onSubmitSuccess, ); }; - - - - - -export default NewVolunteerModal; \ No newline at end of file +export default NewVolunteerModal; diff --git a/apps/frontend/src/components/forms/orderDetailsModal.tsx b/apps/frontend/src/components/forms/orderDetailsModal.tsx new file mode 100644 index 00000000..672fefe0 --- /dev/null +++ b/apps/frontend/src/components/forms/orderDetailsModal.tsx @@ -0,0 +1,150 @@ +import React, { useState, useEffect } from 'react'; +import { + Text, + Dialog, + CloseButton, + Flex, + Textarea, + Field, + Tag, +} from '@chakra-ui/react'; +import ApiClient from '@api/apiClient'; +import { FoodRequest, OrderSummary } from 'types/types'; +import { formatDate } from '@utils/utils'; + +interface OrderDetailsModalProps { + order: OrderSummary; + isOpen: boolean; + onClose: () => void; +} + +const OrderDetailsModal: React.FC = ({ + order, + isOpen, + onClose, +}) => { + const [foodRequest, setFoodRequest] = useState(null); + + useEffect(() => { + if (isOpen) { + const fetchData = async () => { + try { + const foodRequestData = await ApiClient.getFoodRequestFromOrder( + order.orderId, + ); + setFoodRequest(foodRequestData); + } catch (error) { + alert('Error fetching food request details:' + error); + } + }; + + fetchData(); + } + }, [isOpen, order.orderId]); + + return ( + { + if (!e.open) onClose(); + }} + closeOnInteractOutside + > + + + + + + Order {order.orderId} + + + + {foodRequest && ( + <> + + {order.pantry.pantryName} + + + Requested {formatDate(foodRequest.requestedAt)} + + + + + + Size of Shipment + + + + {foodRequest.requestedSize} + + + + + + + Food Type(s) + + + + {foodRequest.requestedItems.map((item, index) => ( + + {item} + + ))} + + + + + + + Additional Information + + +