From 4cfd4a041a637985a305fa21a059f8dcb789a005 Mon Sep 17 00:00:00 2001 From: Justin Wang Date: Mon, 16 Feb 2026 13:24:02 -0500 Subject: [PATCH 1/5] frontend skeleton for order details modal --- .../foodRequests/dtos/order-details.dto.ts | 1 + .../src/foodRequests/request.service.ts | 1 + apps/backend/src/orders/order.controller.ts | 8 + apps/backend/src/orders/order.service.ts | 37 +- apps/frontend/src/api/apiClient.ts | 6 + .../components/forms/orderDetailsModal.tsx | 330 +++++++++++++----- .../src/containers/adminOrderManagement.tsx | 2 +- apps/frontend/src/types/types.ts | 1 + 8 files changed, 299 insertions(+), 87 deletions(-) diff --git a/apps/backend/src/foodRequests/dtos/order-details.dto.ts b/apps/backend/src/foodRequests/dtos/order-details.dto.ts index 21d360ec..0ab12e13 100644 --- a/apps/backend/src/foodRequests/dtos/order-details.dto.ts +++ b/apps/backend/src/foodRequests/dtos/order-details.dto.ts @@ -11,5 +11,6 @@ export class OrderDetailsDto { orderId: number; status: OrderStatus; foodManufacturerName: string; + trackingLink: string; items: OrderItemDetailsDto[]; } diff --git a/apps/backend/src/foodRequests/request.service.ts b/apps/backend/src/foodRequests/request.service.ts index 80093c58..2a22350b 100644 --- a/apps/backend/src/foodRequests/request.service.ts +++ b/apps/backend/src/foodRequests/request.service.ts @@ -65,6 +65,7 @@ export class RequestsService { orderId: order.orderId, status: order.status, foodManufacturerName: order.foodManufacturer.foodManufacturerName, + trackingLink: order.trackingLink, items: order.allocations.map((allocation) => ({ name: allocation.item.itemName, quantity: allocation.allocatedQuantity, diff --git a/apps/backend/src/orders/order.controller.ts b/apps/backend/src/orders/order.controller.ts index a3ec003a..053af6a0 100644 --- a/apps/backend/src/orders/order.controller.ts +++ b/apps/backend/src/orders/order.controller.ts @@ -17,6 +17,7 @@ import { FoodRequest } from '../foodRequests/request.entity'; import { AllocationsService } from '../allocations/allocations.service'; import { OrderStatus } from './types'; import { TrackingCostDto } from './dtos/tracking-cost.dto'; +import { OrderDetailsDto } from '../foodRequests/dtos/order-details.dto'; @Controller('orders') export class OrdersController { @@ -77,6 +78,13 @@ export class OrdersController { return this.ordersService.findOne(orderId); } + @Get('/:orderId/order-details') + async getOrderDetails( + @Param('orderId', ParseIntPipe) orderId: number, + ): Promise { + return this.ordersService.findOrderDetails(orderId); + } + @Get('/order/:requestId') async getOrderByRequestId( @Param('requestId', ParseIntPipe) requestId: number, diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index 4948f2fd..57fad93b 100644 --- a/apps/backend/src/orders/order.service.ts +++ b/apps/backend/src/orders/order.service.ts @@ -12,6 +12,7 @@ import { FoodRequest } from '../foodRequests/request.entity'; import { validateId } from '../utils/validation.utils'; import { OrderStatus } from './types'; import { TrackingCostDto } from './dtos/tracking-cost.dto'; +import { OrderDetailsDto } from '../foodRequests/dtos/order-details.dto'; @Injectable() export class OrdersService { @@ -75,6 +76,36 @@ export class OrdersService { return order; } + async findOrderDetails(orderId: number): Promise { + validateId(orderId, 'Order'); + + const order = await this.repo.findOne({ + where: { orderId }, + relations: { + allocations: { + item: true, + }, + foodManufacturer: true, + }, + }); + + if (!order) { + throw new NotFoundException(`Order ${orderId} not found`); + } + + return ({ + orderId: order.orderId, + status: order.status, + foodManufacturerName: order.foodManufacturer?.foodManufacturerName, + trackingLink: order.trackingLink, + items: order.allocations.map((allocation) => ({ + name: allocation.item.itemName, + quantity: allocation.allocatedQuantity, + foodType: allocation.item.foodType, + })), + }) + } + async findOrderByRequest(requestId: number): Promise { validateId(requestId, 'Request'); @@ -109,7 +140,11 @@ export class OrdersService { const order = await this.repo.findOne({ where: { orderId }, - relations: ['request'], + relations: { + request: { + pantry: true, + }, + }, }); if (!order) { diff --git a/apps/frontend/src/api/apiClient.ts b/apps/frontend/src/api/apiClient.ts index 09a31745..a1951b2f 100644 --- a/apps/frontend/src/api/apiClient.ts +++ b/apps/frontend/src/api/apiClient.ts @@ -236,6 +236,12 @@ export class ApiClient { .then((response) => response.data) as Promise; } + public async getOrderDetails(orderId: number): Promise { + return this.axiosInstance + .get(`/api/orders/${orderId}/order-details`) + .then((response) => response.data) as Promise; + } + async getAllAllocationsByOrder(orderId: number): Promise { return this.axiosInstance .get(`/api/orders/${orderId}/allocations`) diff --git a/apps/frontend/src/components/forms/orderDetailsModal.tsx b/apps/frontend/src/components/forms/orderDetailsModal.tsx index 917bc484..34ef0c70 100644 --- a/apps/frontend/src/components/forms/orderDetailsModal.tsx +++ b/apps/frontend/src/components/forms/orderDetailsModal.tsx @@ -1,36 +1,41 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import { Text, Dialog, CloseButton, Flex, - Textarea, Field, Tag, + Box, + Badge, + Tabs, + Menu, + Link, } from '@chakra-ui/react'; import ApiClient from '@api/apiClient'; -import { FoodRequest, OrderSummary } from 'types/types'; -import { formatDate } from '@utils/utils'; +import { FoodRequest, FoodTypes, OrderDetails, OrderItemDetails } from 'types/types'; +import { OrderStatus } from '../../types/types'; interface OrderDetailsModalProps { - order: OrderSummary; + orderId: number; isOpen: boolean; onClose: () => void; } const OrderDetailsModal: React.FC = ({ - order, + orderId, isOpen, onClose, }) => { const [foodRequest, setFoodRequest] = useState(null); + const [orderDetails, setOrderDetails] = useState(); useEffect(() => { if (isOpen) { - const fetchData = async () => { + const fetchRequestData = async () => { try { const foodRequestData = await ApiClient.getFoodRequestFromOrder( - order.orderId, + orderId, ); setFoodRequest(foodRequestData); } catch (error) { @@ -38,9 +43,53 @@ const OrderDetailsModal: React.FC = ({ } }; - fetchData(); + fetchRequestData(); } - }, [isOpen, order.orderId]); + }, [isOpen, orderId]); + + useEffect(() => { + if (isOpen) { + const fetchOrderDetails = async () => { + try { + const orderDetailsData = await ApiClient.getOrderDetails( + orderId, + ); + setOrderDetails(orderDetailsData); + } catch (error) { + alert('Error fetching order details:' + error); + } + }; + + fetchOrderDetails(); + } + }, [isOpen, orderId]); + + const groupedOrderItemsByType = useMemo(() => { + if (!orderDetails) return {}; + + return orderDetails.items.reduce( + (acc: Record<(typeof FoodTypes)[number], OrderItemDetails[]>, item) => { + if (!acc[item.foodType]) acc[item.foodType] = []; + acc[item.foodType].push(item); + return acc; + }, + {} as Record<(typeof FoodTypes)[number], OrderItemDetails[]>, + ); + }, [orderDetails]); + + const sectionTitleStyles = { + textStyle: 'p2', + fontWeight: '600', + color: 'neutral.800', + }; + + const badgeStyles = { + py: '1', + px: '2', + textStyle: 'p2', + fontSize: '12px', + fontWeight: '500', + }; return ( = ({ - - Order {order.orderId} + + Order #{orderId} - {foodRequest && ( - <> - - {order.request.pantry.pantryName} - - - Requested {formatDate(foodRequest.requestedAt)} - + + Fulfilled by {orderDetails?.foodManufacturerName} + - - - - Size of Shipment - - - - {foodRequest.requestedSize} + + + + Order Details + + + Associated Request + + + + {!foodRequest && ( + + {' '} + No associated food request to display{' '} - + )} + + {foodRequest && ( + + + + Request {foodRequest.requestId} - + + {' '} + {foodRequest.pantry?.pantryName} + + + {orderDetails?.status === OrderStatus.DELIVERED ? ( + + Closed + + ) : ( + + Active + + )} + - - - - Food Type(s) - - - - {foodRequest.requestedItems.map((item, index) => ( - - {item} - - ))} - - + + + Size of Shipment + + + + {foodRequest.requestedSize} + + + + + + + + Food Type(s) + + + + {foodRequest.requestedItems.length > 0 && ( + + {foodRequest.requestedItems.map((item) => ( + + {item} + + ))} + + )} + + + + + + Additional Information + + + + {foodRequest.additionalInformation} + + + + )} + - - - - Additional Information - - -