From 1697cbe9e5f957e920047c90fc2d096b2c7d4010 Mon Sep 17 00:00:00 2001 From: Dalton Burkhart Date: Tue, 7 Oct 2025 22:04:38 -0400 Subject: [PATCH 01/29] Fixed branch issues ignoring lint (Chakra components still having weird error) --- .../src/allocations/allocations.controller.ts | 7 - .../src/allocations/allocations.module.ts | 1 + .../src/allocations/allocations.service.ts | 11 +- apps/backend/src/orders/order.controller.ts | 37 +- apps/backend/src/orders/order.module.ts | 3 +- apps/backend/src/orders/order.service.ts | 102 ++-- apps/frontend/src/app.tsx | 5 + .../forms/deliveryConfirmationModal.tsx | 16 +- .../components/forms/donationDetailsModal.tsx | 171 +++--- .../components/forms/newDonationFormModal.tsx | 16 +- .../components/forms/orderDetailsModal.tsx | 138 +++++ .../forms/orderInformationModal.tsx | 12 +- .../forms/pantryApplicationForm.tsx | 6 +- .../forms/pantryApplicationModal.tsx | 7 +- .../src/components/forms/requestFormModal.tsx | 36 +- .../src/containers/adminOrderManagement.tsx | 505 ++++++++++++++++++ apps/frontend/src/main.tsx | 2 +- apps/frontend/src/theme.ts | 2 +- apps/frontend/src/types/types.ts | 13 +- 19 files changed, 917 insertions(+), 173 deletions(-) create mode 100644 apps/frontend/src/components/forms/orderDetailsModal.tsx create mode 100644 apps/frontend/src/containers/adminOrderManagement.tsx diff --git a/apps/backend/src/allocations/allocations.controller.ts b/apps/backend/src/allocations/allocations.controller.ts index 44776a3d..d8d2324d 100644 --- a/apps/backend/src/allocations/allocations.controller.ts +++ b/apps/backend/src/allocations/allocations.controller.ts @@ -5,11 +5,4 @@ import { Allocation } from './allocations.entity'; @Controller('allocations') export class AllocationsController { constructor(private allocationsService: AllocationsService) {} - - @Get(':orderId/get-all-allocations') - async getAllAllocationsByOrder( - @Param('orderId', ParseIntPipe) orderId: number, - ): Promise { - return this.allocationsService.getAllAllocationsByOrder(orderId); - } } diff --git a/apps/backend/src/allocations/allocations.module.ts b/apps/backend/src/allocations/allocations.module.ts index 26e36b74..fed7360b 100644 --- a/apps/backend/src/allocations/allocations.module.ts +++ b/apps/backend/src/allocations/allocations.module.ts @@ -10,5 +10,6 @@ import { JwtStrategy } from '../auth/jwt.strategy'; imports: [TypeOrmModule.forFeature([Allocation])], controllers: [AllocationsController], providers: [AllocationsService, AuthService, JwtStrategy], + exports: [AllocationsService], }) export class AllocationModule {} diff --git a/apps/backend/src/allocations/allocations.service.ts b/apps/backend/src/allocations/allocations.service.ts index a2797376..d5bbf460 100644 --- a/apps/backend/src/allocations/allocations.service.ts +++ b/apps/backend/src/allocations/allocations.service.ts @@ -11,10 +11,17 @@ export class AllocationsService { async getAllAllocationsByOrder( orderId: number, - ): Promise { + ): Promise[]> { return this.repo.find({ - where: { orderId: orderId }, + where: { orderId }, relations: ['item'], + select: { + allocationId: true, + allocatedQuantity: true, + reservedAt: true, + fulfilledAt: true, + status: true, + }, }); } } diff --git a/apps/backend/src/orders/order.controller.ts b/apps/backend/src/orders/order.controller.ts index 9b8579d0..596f7fbd 100644 --- a/apps/backend/src/orders/order.controller.ts +++ b/apps/backend/src/orders/order.controller.ts @@ -1,10 +1,12 @@ import { Controller, Get, + Post, Patch, Param, ParseIntPipe, Body, + Query, } from '@nestjs/common'; import { OrdersService } from './order.service'; import { Order } from './order.entity'; @@ -12,14 +14,24 @@ 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'; @Controller('orders') export class OrdersController { - constructor(private ordersService: OrdersService) {} + constructor( + private readonly ordersService: OrdersService, + private readonly allocationsService: AllocationsService, + ) {} @Get('/get-all-orders') - async getAllOrders(): Promise { - return this.ordersService.getAll(); + async getAllOrders( + @Query('status') status?: string, + @Query('pantryName') pantryNames?: string | string[], + ): Promise { + if (typeof pantryNames === 'string') { + pantryNames = [pantryNames]; + } + return this.ordersService.getAll({ status, pantryNames }); } @Get('/get-current-orders') @@ -35,28 +47,28 @@ export class OrdersController { @Get(':orderId/pantry') async getPantryFromOrder( @Param('orderId', ParseIntPipe) orderId: number, - ): Promise { + ): Promise { return this.ordersService.findOrderPantry(orderId); } @Get(':orderId/request') async getRequestFromOrder( @Param('orderId', ParseIntPipe) orderId: number, - ): Promise { + ): Promise { return this.ordersService.findOrderFoodRequest(orderId); } @Get(':orderId/manufacturer') async getManufacturerFromOrder( @Param('orderId', ParseIntPipe) orderId: number, - ): Promise { + ): Promise { return this.ordersService.findOrderFoodManufacturer(orderId); } @Get(':orderId/donation') async getDonationFromOrder( @Param('orderId', ParseIntPipe) orderId: number, - ): Promise { + ): Promise { return this.ordersService.findOrderDonation(orderId); } @@ -69,9 +81,16 @@ export class OrdersController { @Get('/order/:requestId') async getOrderByRequestId( - @Param('requestId', ParseIntPipe) requestId: number, + @Param('orderId', ParseIntPipe) orderId: number, ): Promise { - return this.ordersService.findOrderByRequest(requestId); + return this.ordersService.findOrderByRequest(orderId); + } + + @Get(':orderId/get-all-allocations') + async getAllAllocationsByOrder( + @Param('orderId', ParseIntPipe) orderId: number, + ) { + return this.allocationsService.getAllAllocationsByOrder(orderId); } @Patch('/update-status/:orderId') diff --git a/apps/backend/src/orders/order.module.ts b/apps/backend/src/orders/order.module.ts index fe2d5584..bf07f1cd 100644 --- a/apps/backend/src/orders/order.module.ts +++ b/apps/backend/src/orders/order.module.ts @@ -5,9 +5,10 @@ import { Order } from './order.entity'; import { OrdersService } from './order.service'; import { JwtStrategy } from '../auth/jwt.strategy'; import { AuthService } from '../auth/auth.service'; +import { AllocationModule } from '../allocations/allocations.module'; @Module({ - imports: [TypeOrmModule.forFeature([Order])], + imports: [TypeOrmModule.forFeature([Order]), AllocationModule], controllers: [OrdersController], providers: [OrdersService, AuthService, JwtStrategy], }) diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index ad1e280c..17ca3232 100644 --- a/apps/backend/src/orders/order.service.ts +++ b/apps/backend/src/orders/order.service.ts @@ -6,14 +6,40 @@ 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 { validateId } from '../utils/validation.utils'; @Injectable() export class OrdersService { constructor(@InjectRepository(Order) private repo: Repository) {} - async getAll() { - return this.repo.find(); + async getAll(filters?: { status?: string; pantryNames?: string[] }) { + const qb = this.repo + .createQueryBuilder('order') + .leftJoinAndSelect('order.pantry', 'pantry') + .leftJoinAndSelect('pantry.ssfRepresentative', 'ssfRepresentative') + .select([ + 'order.orderId', + 'order.status', + 'order.createdAt', + 'order.shippedAt', + 'order.deliveredAt', + 'pantry.pantryName', + 'pantry.ssfRepresentative.id', + 'ssfRepresentative.firstName', + 'ssfRepresentative.lastName', + 'ssfRepresentative.email', + ]); + + if (filters?.status) { + qb.andWhere('order.status = :status', { status: filters.status }); + } + + if (filters?.pantryNames) { + qb.andWhere('pantry.pantryName IN (:...pantryNames)', { + pantryNames: filters.pantryNames, + }); + } + + return qb.getMany(); } async getCurrentOrders() { @@ -28,88 +54,78 @@ export class OrdersService { }); } - async findOne(orderId: number): Promise { - validateId(orderId, 'Order'); - - const order = await this.repo.findOneBy({ orderId }); - - if (!order) { - throw new NotFoundException(`Order ${orderId} not found`); + async findOne(orderId: number) { + if (!orderId || orderId < 1) { + throw new NotFoundException('Invalid order ID'); } - return order; + return await this.repo.findOne({ + where: { orderId }, + }); } - async findOrderByRequest(requestId: number): Promise { - validateId(requestId, 'Request'); - + async findOrderByRequest(requestId: number): Promise { const order = await this.repo.findOne({ where: { requestId }, relations: ['request'], }); if (!order) { - throw new NotFoundException( - `Order with request ID ${requestId} not found`, - ); + return null; + } else { + return order; } - return order; } - async findOrderPantry(orderId: number): Promise { - validateId(orderId, 'Order'); - + async findOrderPantry(orderId: number): Promise { const order = await this.repo.findOne({ where: { orderId }, relations: ['pantry'], }); if (!order) { - throw new NotFoundException(`Order ${orderId} not found`); + return null; + } else { + return order.pantry; } - return order.pantry; } - async findOrderFoodRequest(orderId: number): Promise { - validateId(orderId, 'Order'); - + async findOrderFoodRequest(orderId: number): Promise { const order = await this.repo.findOne({ where: { orderId }, relations: ['request'], }); if (!order) { - throw new NotFoundException(`Order ${orderId} not found`); + return null; + } else { + return order.request; } - return order.request; } - async findOrderFoodManufacturer(orderId: number): Promise { - validateId(orderId, 'Order'); - - const order = await this.findOne(orderId); - - if (!order) { - throw new NotFoundException(`Order ${orderId} not found`); - } - return order.foodManufacturer; + async findOrderFoodManufacturer( + orderId: number, + ): Promise { + const order = this.findOne(orderId); + return (await order).foodManufacturer; } - async findOrderDonation(orderId: number): Promise { - validateId(orderId, 'Order'); - + async findOrderDonation(orderId: number): Promise { const order = await this.repo.findOne({ where: { orderId }, relations: ['donation'], }); if (!order) { - throw new NotFoundException(`Order ${orderId} not found`); + return null; + } else { + return order.donation; } - return order.donation; } async updateStatus(orderId: number, newStatus: string) { - validateId(orderId, 'Order'); + if (!orderId || orderId < 1) { + throw new NotFoundException('Invalid order ID'); + } // TODO: Once we start navigating to proper food manufacturer page, change the 1 to be the proper food manufacturer id await this.repo diff --git a/apps/frontend/src/app.tsx b/apps/frontend/src/app.tsx index 6dfdeecb..ac572985 100644 --- a/apps/frontend/src/app.tsx +++ b/apps/frontend/src/app.tsx @@ -20,6 +20,7 @@ import VolunteerManagement from '@containers/volunteerManagement'; import FoodManufacturerOrderDashboard from '@containers/foodManufacturerOrderDashboard'; import DonationManagement from '@containers/donationManagement'; import AdminDonation from '@containers/adminDonation'; +import AdminOrderManagement from '@containers/adminOrderManagement'; const router = createBrowserRouter([ { @@ -84,6 +85,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/deliveryConfirmationModal.tsx b/apps/frontend/src/components/forms/deliveryConfirmationModal.tsx index 718a540f..b9431fb8 100644 --- a/apps/frontend/src/components/forms/deliveryConfirmationModal.tsx +++ b/apps/frontend/src/components/forms/deliveryConfirmationModal.tsx @@ -55,7 +55,11 @@ const DeliveryConfirmationModal: React.FC = ({ }; return ( - !e.open && onClose()} size="xl"> + !e.open && onClose()} + size="xl" + > @@ -76,7 +80,11 @@ const DeliveryConfirmationModal: React.FC = ({ Delivery Date - + = ({ accept=".jpg,.jpeg,.png" onChange={handlePhotoChange} /> - Select up to 3 photos to upload. + + Select up to 3 photos to upload. + {renderPhotoNames()} diff --git a/apps/frontend/src/components/forms/donationDetailsModal.tsx b/apps/frontend/src/components/forms/donationDetailsModal.tsx index 41c8f84e..d37e2d8d 100644 --- a/apps/frontend/src/components/forms/donationDetailsModal.tsx +++ b/apps/frontend/src/components/forms/donationDetailsModal.tsx @@ -1,5 +1,12 @@ import React, { useState, useEffect } from 'react'; -import { Box, Text, VStack, Dialog, Portal, CloseButton } from '@chakra-ui/react'; +import { + Box, + Text, + VStack, + Dialog, + Portal, + CloseButton, +} from '@chakra-ui/react'; import ApiClient from '@api/apiClient'; import { Donation } from 'types/types'; import { DonationItem } from 'types/types'; @@ -19,13 +26,15 @@ const DonationDetailsModal: React.FC = ({ const [donation, setDonation] = useState(null); const [items, setItems] = useState([]); - useEffect(() => { + useEffect(() => { if (isOpen) { const fetchData = async () => { try { const donationData = await ApiClient.getOrderDonation(donationId); - const itemsData = await ApiClient.getDonationItemsByDonationId(donationId); - + const itemsData = await ApiClient.getDonationItemsByDonationId( + donationId, + ); + setDonation(donationData); setItems(itemsData); } catch (error) { @@ -43,83 +52,95 @@ const DonationDetailsModal: React.FC = ({ } acc[item.foodType].push(item); return acc; - }, {} as Record) + }, {} as Record); return ( !e.open && onClose()}> - - - - - - - + + + + + + + - - - - Donation #{donationId} Details - - {donation && ( - <> - - {donation.foodManufacturer?.foodManufacturerName} - - - {formatDate(donation.dateDonated)} - - - )} - - - - - {donation && ( - - {Object.entries(groupedItems).map(([foodType, typeItems]) => ( - - - {foodType} + + + + Donation #{donationId} Details + + {donation && ( + <> + + {donation.foodManufacturer?.foodManufacturerName} + + + {formatDate(donation.dateDonated)} + + + )} + + + + + {donation && ( + + {Object.entries(groupedItems).map(([foodType, typeItems]) => ( + + + {foodType} + + + {typeItems.map((item, index) => ( + + + + {item.itemName} - - {typeItems.map((item, index) => ( - - - - {item.itemName} - - - - {item.quantity} - - - ))} - - ))} - - )} - - - - + + + {item.quantity} + + + + ))} + + + ))} + + )} + + + + ); }; diff --git a/apps/frontend/src/components/forms/newDonationFormModal.tsx b/apps/frontend/src/components/forms/newDonationFormModal.tsx index 4d6e805a..100612b6 100644 --- a/apps/frontend/src/components/forms/newDonationFormModal.tsx +++ b/apps/frontend/src/components/forms/newDonationFormModal.tsx @@ -171,7 +171,11 @@ const NewDonationFormModal: React.FC = ({ }; return ( - !e.open && onClose()}> + !e.open && onClose()} + > @@ -186,7 +190,13 @@ const NewDonationFormModal: React.FC = ({ Please make sure to fill out all fields before submitting. Log a new donation - + @@ -221,7 +231,7 @@ const NewDonationFormModal: React.FC = ({ - diff --git a/apps/frontend/src/components/forms/orderDetailsModal.tsx b/apps/frontend/src/components/forms/orderDetailsModal.tsx new file mode 100644 index 00000000..bb2f9168 --- /dev/null +++ b/apps/frontend/src/components/forms/orderDetailsModal.tsx @@ -0,0 +1,138 @@ +import React, { useState, useEffect } from 'react'; +import { + Box, + Text, + VStack, + Dialog, + Portal, + CloseButton, +} from '@chakra-ui/react'; +import ApiClient from '@api/apiClient'; +import { Donation, FoodRequest, Order } from 'types/types'; +import { DonationItem } from 'types/types'; +import { formatDate } from '@utils/utils'; + +interface OrderDetailsModalProps { + orderId: number; + isOpen: boolean; + onClose: () => void; +} + +const OrderDetailsModal: React.FC = ({ + orderId, + isOpen, + onClose, +}) => { + const [foodRequest, setFoodRequest] = useState(null); + + useEffect(() => { + if (isOpen) { + const fetchData = async () => { + try { + const foodRequestData = await ApiClient.getFoodRequestFromOrder( + orderId, + ); + setFoodRequest(foodRequestData); + } catch (error) { + console.error('Error fetching food request details:', error); + } + }; + + fetchData(); + } + }, [isOpen, orderId]); + + return ( + !e.open && onClose()}> + + + + + + + + + + + + Order #{orderId} Details + + {foodRequest && ( + <> + + Request Id #{foodRequest.requestId} + + + Requested {formatDate(foodRequest.requestedAt)} + + + )} + + + + + {foodRequest && ( + + + + Size of Shipment + + + {foodRequest.requestedSize} + + + + + Food Type(s) + + + {foodRequest.requestedItems.map((type, index) => ( + + + {type} + + + x + + + ))} + + + + + Additional Information + + + {foodRequest.additionalInformation || + 'No details provided.'} + + + + )} + + + + + + ); +}; + +export default OrderDetailsModal; diff --git a/apps/frontend/src/components/forms/orderInformationModal.tsx b/apps/frontend/src/components/forms/orderInformationModal.tsx index d737caf0..4d7437f2 100644 --- a/apps/frontend/src/components/forms/orderInformationModal.tsx +++ b/apps/frontend/src/components/forms/orderInformationModal.tsx @@ -1,8 +1,4 @@ -import { - VStack, - Text, - Dialog, -} from '@chakra-ui/react'; +import { VStack, Text, Dialog } from '@chakra-ui/react'; import { useState, useEffect } from 'react'; import ApiClient from '@api/apiClient'; import { Pantry, Allocation } from 'types/types'; @@ -42,7 +38,11 @@ const OrderInformationModal: React.FC = ({ }, [isOpen, orderId]); return ( - !e.open && onClose()}> + !e.open && onClose()} + > diff --git a/apps/frontend/src/components/forms/pantryApplicationForm.tsx b/apps/frontend/src/components/forms/pantryApplicationForm.tsx index c9c700b1..b0ea64c7 100644 --- a/apps/frontend/src/components/forms/pantryApplicationForm.tsx +++ b/apps/frontend/src/components/forms/pantryApplicationForm.tsx @@ -221,7 +221,11 @@ const PantryApplicationForm: React.FC = () => { ...otherDietaryRestrictionsOptions, 'Unsure', ].map((value) => ( - + {value} diff --git a/apps/frontend/src/components/forms/pantryApplicationModal.tsx b/apps/frontend/src/components/forms/pantryApplicationModal.tsx index 0a0940bc..38c698de 100644 --- a/apps/frontend/src/components/forms/pantryApplicationModal.tsx +++ b/apps/frontend/src/components/forms/pantryApplicationModal.tsx @@ -1,10 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { - Button, - Dialog, - Grid, - GridItem, -} from '@chakra-ui/react'; +import { Button, Dialog, Grid, GridItem } from '@chakra-ui/react'; import ApiClient from '@api/apiClient'; import { Pantry, User } from 'types/types'; diff --git a/apps/frontend/src/components/forms/requestFormModal.tsx b/apps/frontend/src/components/forms/requestFormModal.tsx index 94e79499..1ad255ce 100644 --- a/apps/frontend/src/components/forms/requestFormModal.tsx +++ b/apps/frontend/src/components/forms/requestFormModal.tsx @@ -71,7 +71,11 @@ const FoodRequestFormModal: React.FC = ({ ]; return ( - !e.open && onClose()}> + !e.open && onClose()} + > @@ -87,9 +91,9 @@ const FoodRequestFormModal: React.FC = ({ needs.

- Please keep in mind that we may not be able to accommodate specific - food requests at all times, but we will do our best to match your - preferences. + Please keep in mind that we may not be able to accommodate + specific food requests at all times, but we will do our best to + match your preferences.
@@ -98,7 +102,11 @@ const FoodRequestFormModal: React.FC = ({ Requested Size of Shipment - + = ({ - {option.label} + + {option.label} + ))} @@ -123,7 +133,11 @@ const FoodRequestFormModal: React.FC = ({ Requested Shipment - + = ({ > - {allergen} + {allergen} ))} @@ -151,7 +165,11 @@ const FoodRequestFormModal: React.FC = ({ Additional Comments - +