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.controller.spec.ts b/apps/backend/src/foodRequests/request.controller.spec.ts index 45b0d2d2..7496db33 100644 --- a/apps/backend/src/foodRequests/request.controller.spec.ts +++ b/apps/backend/src/foodRequests/request.controller.spec.ts @@ -102,6 +102,7 @@ describe('RequestsController', () => { orderId: 10, status: OrderStatus.DELIVERED, foodManufacturerName: 'Test Manufacturer', + trackingLink: 'examplelink.com', items: [ { name: 'Rice', @@ -119,6 +120,7 @@ describe('RequestsController', () => { orderId: 11, status: OrderStatus.PENDING, foodManufacturerName: 'Another Manufacturer', + trackingLink: 'examplelink.com', items: [ { name: 'Milk', diff --git a/apps/backend/src/foodRequests/request.service.ts b/apps/backend/src/foodRequests/request.service.ts index c74b2f87..089c707a 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.spec.ts b/apps/backend/src/orders/order.controller.spec.ts index 4706f451..05529931 100644 --- a/apps/backend/src/orders/order.controller.spec.ts +++ b/apps/backend/src/orders/order.controller.spec.ts @@ -9,6 +9,8 @@ import { OrderStatus } from './types'; import { FoodRequest } from '../foodRequests/request.entity'; import { Pantry } from '../pantries/pantries.entity'; import { TrackingCostDto } from './dtos/tracking-cost.dto'; +import { OrderDetailsDto } from '../foodRequests/dtos/order-details.dto'; +import { FoodType } from '../donationItems/types'; import { BadRequestException, NotFoundException } from '@nestjs/common'; import { FoodManufacturer } from '../foodManufacturers/manufacturers.entity'; @@ -62,6 +64,22 @@ describe('OrdersController', () => { { allocationId: 3, orderId: 2 }, ]; + const mockOrderDetails: Partial[] = [ + { + orderId: 1, + status: OrderStatus.DELIVERED, + foodManufacturerName: 'food manufacturer 1', + trackingLink: 'example-link.com', + items: [ + { + name: 'item1', + quantity: 10, + foodType: FoodType.DAIRY_FREE_ALTERNATIVES, + }, + ], + }, + ]; + beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [OrdersController], @@ -78,6 +96,21 @@ describe('OrdersController', () => { expect(controller).toBeDefined(); }); + describe('getOrderDetails', () => { + it('should call ordersService.findOrderDetails and return order details', async () => { + mockOrdersService.findOrderDetails.mockResolvedValueOnce( + mockOrderDetails[0] as OrderDetailsDto, + ); + + const orderId = 1; + + const result = await controller.getOrderDetails(orderId); + + expect(result).toEqual(mockOrderDetails[0] as OrderDetailsDto); + expect(mockOrdersService.findOrderDetails).toHaveBeenCalledWith(orderId); + }); + }); + describe('getAllOrders', () => { it('should call ordersService.getAll and return orders', async () => { const status = 'pending'; 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.spec.ts b/apps/backend/src/orders/order.service.spec.ts index 808ef510..8a4a7547 100644 --- a/apps/backend/src/orders/order.service.spec.ts +++ b/apps/backend/src/orders/order.service.spec.ts @@ -5,6 +5,7 @@ import { Order } from './order.entity'; import { testDataSource } from '../config/typeormTestDataSource'; import { OrderStatus } from './types'; import { Pantry } from '../pantries/pantries.entity'; +import { OrderDetailsDto } from '../foodRequests/dtos/order-details.dto'; import { BadRequestException, NotFoundException } from '@nestjs/common'; import { TrackingCostDto } from './dtos/tracking-cost.dto'; @@ -124,274 +125,325 @@ describe('OrdersService', () => { }); }); - describe('getCurrentOrders', () => { - it(`returns only orders with status 'pending' or 'shipped'`, async () => { - const orders = await service.getCurrentOrders(); - expect(orders).toHaveLength(2); - expect( - orders.every( - (order) => - order.status === OrderStatus.PENDING || - order.status === OrderStatus.SHIPPED, - ), - ).toBe(true); - }); - }); + describe('findOrderDetails', () => { + it('returns mapped OrderDetailsDto including allocations and manufacturer', async () => { + const orderRepo = testDataSource.getRepository(Order); - describe('getPastOrders', () => { - it(`returns only orders with status 'delivered'`, async () => { - const orders = await service.getPastOrders(); - expect(orders).toHaveLength(2); - expect( - orders.every((order) => order.status === OrderStatus.DELIVERED), - ).toBe(true); - }); - }); + const seededOrder = await orderRepo.findOne({ + where: {}, + relations: { + allocations: { item: true }, + foodManufacturer: true, + }, + }); + + expect(seededOrder).toBeTruthy(); + expect(seededOrder!.allocations?.length).toBeGreaterThan(0); - describe('findOne', () => { - it('returns order by ID', async () => { - const orderId = 1; - const result = await service.findOne(orderId); + const orderId = seededOrder!.orderId; - expect(result).toBeDefined(); - expect(result.orderId).toBe(1); + const result = await service.findOrderDetails(orderId); + + const expected: OrderDetailsDto = { + orderId: seededOrder!.orderId, + status: seededOrder!.status, + foodManufacturerName: + seededOrder!.foodManufacturer?.foodManufacturerName, + trackingLink: seededOrder!.trackingLink, + items: seededOrder!.allocations.map((allocation) => ({ + name: allocation.item.itemName, + quantity: allocation.allocatedQuantity, + foodType: allocation.item.foodType, + })), + }; + + expect(result).toEqual(expected); }); - it('throws NotFoundException for non-existent order', async () => { - await expect(service.findOne(9999)).rejects.toThrow( - new NotFoundException('Order 9999 not found'), + it('throws NotFoundException when order does not exist', async () => { + const missingOrderId = 99999999; + + await expect(service.findOrderDetails(missingOrderId)).rejects.toThrow( + NotFoundException, + ); + await expect(service.findOrderDetails(missingOrderId)).rejects.toThrow( + `Order ${missingOrderId} not found`, ); }); - }); - - describe('findOrderByRequest', () => { - it('returns order by request ID', async () => { - const order = await service.findOrderByRequest(1); - expect(order).toBeDefined(); - expect(order.request).toBeDefined(); - expect(order.requestId).toBe(1); + describe('getCurrentOrders', () => { + it(`returns only orders with status 'pending' or 'shipped'`, async () => { + const orders = await service.getCurrentOrders(); + expect(orders).toHaveLength(2); + expect( + orders.every( + (order) => + order.status === OrderStatus.PENDING || + order.status === OrderStatus.SHIPPED, + ), + ).toBe(true); + }); }); - it('throws NotFoundException for non-existent order', async () => { - await expect(service.findOrderByRequest(9999)).rejects.toThrow( - new NotFoundException('Order with request ID 9999 not found'), - ); + describe('getPastOrders', () => { + it(`returns only orders with status 'delivered'`, async () => { + const orders = await service.getPastOrders(); + expect(orders).toHaveLength(2); + expect( + orders.every((order) => order.status === OrderStatus.DELIVERED), + ).toBe(true); + }); }); - }); - describe('findOrderFoodRequest', () => { - it('returns food request of order', async () => { - const foodRequest = await service.findOrderFoodRequest(1); + describe('findOne', () => { + it('returns order by ID', async () => { + const orderId = 1; + const result = await service.findOne(orderId); - expect(foodRequest).toBeDefined(); - expect(foodRequest.requestId).toBe(1); + expect(result).toBeDefined(); + expect(result.orderId).toBe(1); + }); + + it('throws NotFoundException for non-existent order', async () => { + await expect(service.findOne(9999)).rejects.toThrow( + new NotFoundException('Order 9999 not found'), + ); + }); }); - it('throws NotFoundException for non-existent order', async () => { - await expect(service.findOrderFoodRequest(9999)).rejects.toThrow( - new NotFoundException('Order 9999 not found'), - ); + describe('findOrderByRequest', () => { + it('returns order by request ID', async () => { + const order = await service.findOrderByRequest(1); + + expect(order).toBeDefined(); + expect(order.request).toBeDefined(); + expect(order.requestId).toBe(1); + }); + + it('throws NotFoundException for non-existent order', async () => { + await expect(service.findOrderByRequest(9999)).rejects.toThrow( + new NotFoundException('Order with request ID 9999 not found'), + ); + }); }); - }); - describe('findOrderPantry', () => { - it('returns pantry of order', async () => { - const pantry = await service.findOrderPantry(1); + describe('findOrderFoodRequest', () => { + it('returns food request of order', async () => { + const foodRequest = await service.findOrderFoodRequest(1); + + expect(foodRequest).toBeDefined(); + expect(foodRequest.requestId).toBe(1); + }); - expect(pantry).toBeDefined(); - expect(pantry.pantryName).toEqual('Community Food Pantry Downtown'); - expect(pantry.pantryId).toEqual(1); + it('throws NotFoundException for non-existent order', async () => { + await expect(service.findOrderFoodRequest(9999)).rejects.toThrow( + new NotFoundException('Order 9999 not found'), + ); + }); }); - }); - describe('findOrderFoodManufacturer', () => { - it('returns FM of order', async () => { - const foodManufacturer = await service.findOrderFoodManufacturer(2); + describe('findOrderPantry', () => { + it('returns pantry of order', async () => { + const pantry = await service.findOrderPantry(1); - expect(foodManufacturer).toBeDefined(); - expect(foodManufacturer.foodManufacturerName).toEqual('Healthy Foods Co'); - expect(foodManufacturer.foodManufacturerId).toEqual(2); + expect(pantry).toBeDefined(); + expect(pantry.pantryName).toEqual('Community Food Pantry Downtown'); + expect(pantry.pantryId).toEqual(1); + }); }); - it('throws NotFoundException for non-existent order', async () => { - await expect(service.findOrderFoodManufacturer(9999)).rejects.toThrow( - new NotFoundException('Order 9999 not found'), - ); + describe('findOrderFoodManufacturer', () => { + it('returns FM of order', async () => { + const foodManufacturer = await service.findOrderFoodManufacturer(2); + + expect(foodManufacturer).toBeDefined(); + expect(foodManufacturer.foodManufacturerName).toEqual( + 'Healthy Foods Co', + ); + expect(foodManufacturer.foodManufacturerId).toEqual(2); + }); + + it('throws NotFoundException for non-existent order', async () => { + await expect(service.findOrderFoodManufacturer(9999)).rejects.toThrow( + new NotFoundException('Order 9999 not found'), + ); + }); }); - }); - describe('updateStatus', () => { - it('updates order status to delivered', async () => { - const orderId = 3; - const order = await service.findOne(orderId); + describe('updateStatus', () => { + it('updates order status to delivered', async () => { + const orderId = 3; + const order = await service.findOne(orderId); - expect(order.status).toEqual(OrderStatus.SHIPPED); - expect(order.shippedAt).toBeDefined(); + expect(order.status).toEqual(OrderStatus.SHIPPED); + expect(order.shippedAt).toBeDefined(); - await service.updateStatus(orderId, OrderStatus.DELIVERED); - const updatedOrder = await service.findOne(orderId); + await service.updateStatus(orderId, OrderStatus.DELIVERED); + const updatedOrder = await service.findOne(orderId); - expect(updatedOrder.status).toEqual(OrderStatus.DELIVERED); - expect(updatedOrder.deliveredAt).toBeDefined(); - }); + expect(updatedOrder.status).toEqual(OrderStatus.DELIVERED); + expect(updatedOrder.deliveredAt).toBeDefined(); + }); - it('updates order status to shipped', async () => { - const orderId = 4; - const order = await service.findOne(orderId); + it('updates order status to shipped', async () => { + const orderId = 4; + const order = await service.findOne(orderId); - expect(order.status).toEqual(OrderStatus.PENDING); + expect(order.status).toEqual(OrderStatus.PENDING); - await service.updateStatus(orderId, OrderStatus.SHIPPED); - const updatedOrder = await service.findOne(orderId); + await service.updateStatus(orderId, OrderStatus.SHIPPED); + const updatedOrder = await service.findOne(orderId); - expect(updatedOrder.status).toEqual(OrderStatus.SHIPPED); - expect(updatedOrder.shippedAt).toBeDefined(); - expect(updatedOrder.deliveredAt).toBeNull(); + expect(updatedOrder.status).toEqual(OrderStatus.SHIPPED); + expect(updatedOrder.shippedAt).toBeDefined(); + expect(updatedOrder.deliveredAt).toBeNull(); + }); }); - }); - describe('getOrdersByPantry', () => { - it('returns order from pantry ID', async () => { - const pantryId = 1; - const orders = await service.getOrdersByPantry(pantryId); + describe('getOrdersByPantry', () => { + it('returns order from pantry ID', async () => { + const pantryId = 1; + const orders = await service.getOrdersByPantry(pantryId); - expect(orders.length).toBe(2); - expect(orders.every((order) => order.request)).toBeDefined(); - expect(orders.every((order) => order.request.pantryId === 1)).toBe(true); - }); + expect(orders.length).toBe(2); + expect(orders.every((order) => order.request)).toBeDefined(); + expect(orders.every((order) => order.request.pantryId === 1)).toBe( + true, + ); + }); - it('returns empty list for pantry with no orderes', async () => { - const pantryId = 5; - const orders = await service.getOrdersByPantry(pantryId); + it('returns empty list for pantry with no orderes', async () => { + const pantryId = 5; + const orders = await service.getOrdersByPantry(pantryId); - expect(orders).toEqual([]); - }); + expect(orders).toEqual([]); + }); - it('throws NotFoundException for non-existent pantry', async () => { - const pantryId = 9999; + it('throws NotFoundException for non-existent pantry', async () => { + const pantryId = 9999; - await expect(service.getOrdersByPantry(pantryId)).rejects.toThrow( - new NotFoundException(`Pantry ${pantryId} not found`), - ); + await expect(service.getOrdersByPantry(pantryId)).rejects.toThrow( + new NotFoundException(`Pantry ${pantryId} not found`), + ); + }); }); - }); - describe('updateTrackingCostInfo', () => { - it('throws when order is non-existent', async () => { - const trackingCostDto: TrackingCostDto = { - trackingLink: 'test', - shippingCost: 5.99, - }; + describe('updateTrackingCostInfo', () => { + it('throws when order is non-existent', async () => { + const trackingCostDto: TrackingCostDto = { + trackingLink: 'test', + shippingCost: 5.99, + }; - await expect( - service.updateTrackingCostInfo(9999, trackingCostDto), - ).rejects.toThrow(new NotFoundException('Order 9999 not found')); - }); + await expect( + service.updateTrackingCostInfo(9999, trackingCostDto), + ).rejects.toThrow(new NotFoundException('Order 9999 not found')); + }); - it('throws when tracking link and shipping cost not given', async () => { - await expect(service.updateTrackingCostInfo(3, {})).rejects.toThrow( - new BadRequestException( - 'At least one of tracking link or shipping cost must be provided', - ), - ); - }); + it('throws when tracking link and shipping cost not given', async () => { + await expect(service.updateTrackingCostInfo(3, {})).rejects.toThrow( + new BadRequestException( + 'At least one of tracking link or shipping cost must be provided', + ), + ); + }); - it('updates tracking link for shipped order', async () => { - const trackingCostDto: TrackingCostDto = { - trackingLink: 'samplelink.com', - }; + it('updates tracking link for shipped order', async () => { + const trackingCostDto: TrackingCostDto = { + trackingLink: 'samplelink.com', + }; - await service.updateTrackingCostInfo(3, trackingCostDto); + await service.updateTrackingCostInfo(3, trackingCostDto); - const order = await service.findOne(3); - expect(order.trackingLink).toBeDefined(); - expect(order.trackingLink).toEqual('samplelink.com'); - }); + const order = await service.findOne(3); + expect(order.trackingLink).toBeDefined(); + expect(order.trackingLink).toEqual('samplelink.com'); + }); - it('updates shipping cost for shipped order', async () => { - const trackingCostDto: TrackingCostDto = { - shippingCost: 12.99, - }; + it('updates shipping cost for shipped order', async () => { + const trackingCostDto: TrackingCostDto = { + shippingCost: 12.99, + }; - await service.updateTrackingCostInfo(3, trackingCostDto); + await service.updateTrackingCostInfo(3, trackingCostDto); - const order = await service.findOne(3); - expect(order.shippingCost).toBeDefined(); - expect(order.shippingCost).toEqual('12.99'); - }); + const order = await service.findOne(3); + expect(order.shippingCost).toBeDefined(); + expect(order.shippingCost).toEqual('12.99'); + }); - it('updates both shipping cost and tracking link', async () => { - const trackingCostDto: TrackingCostDto = { - trackingLink: 'testtracking.com', - shippingCost: 7.5, - }; + it('updates both shipping cost and tracking link', async () => { + const trackingCostDto: TrackingCostDto = { + trackingLink: 'testtracking.com', + shippingCost: 7.5, + }; - await service.updateTrackingCostInfo(3, trackingCostDto); + await service.updateTrackingCostInfo(3, trackingCostDto); - const order = await service.findOne(3); - expect(order.trackingLink).toEqual('testtracking.com'); - expect(order.shippingCost).toEqual('7.50'); - }); + const order = await service.findOne(3); + expect(order.trackingLink).toEqual('testtracking.com'); + expect(order.shippingCost).toEqual('7.50'); + }); - it('throws BadRequestException for delivered order', async () => { - const trackingCostDto: TrackingCostDto = { - trackingLink: 'testtracking.com', - shippingCost: 7.5, - }; - const orderId = 2; + it('throws BadRequestException for delivered order', async () => { + const trackingCostDto: TrackingCostDto = { + trackingLink: 'testtracking.com', + shippingCost: 7.5, + }; + const orderId = 2; - const order = await service.findOne(orderId); + const order = await service.findOne(orderId); - expect(order.status).toEqual(OrderStatus.DELIVERED); + expect(order.status).toEqual(OrderStatus.DELIVERED); - await expect( - service.updateTrackingCostInfo(orderId, trackingCostDto), - ).rejects.toThrow( - new BadRequestException( - 'Can only update tracking info for pending or shipped orders', - ), - ); - }); + await expect( + service.updateTrackingCostInfo(orderId, trackingCostDto), + ).rejects.toThrow( + new BadRequestException( + 'Can only update tracking info for pending or shipped orders', + ), + ); + }); - it('throws when both fields are not provided for first time setting', async () => { - const trackingCostDto: TrackingCostDto = { - trackingLink: 'testtracking.com', - }; - const orderId = 4; + it('throws when both fields are not provided for first time setting', async () => { + const trackingCostDto: TrackingCostDto = { + trackingLink: 'testtracking.com', + }; + const orderId = 4; - const order = await service.findOne(orderId); + const order = await service.findOne(orderId); - expect(order.shippedAt).toBeNull(); - expect(order.trackingLink).toBeNull(); + expect(order.shippedAt).toBeNull(); + expect(order.trackingLink).toBeNull(); - await expect( - service.updateTrackingCostInfo(4, trackingCostDto), - ).rejects.toThrow( - new BadRequestException( - 'Must provide both tracking link and shipping cost on initial assignment', - ), - ); - }); + await expect( + service.updateTrackingCostInfo(4, trackingCostDto), + ).rejects.toThrow( + new BadRequestException( + 'Must provide both tracking link and shipping cost on initial assignment', + ), + ); + }); - it('sets status to shipped when both fields provided and previous status pending', async () => { - const trackingCostDto: TrackingCostDto = { - trackingLink: 'testtracking.com', - shippingCost: 5.75, - }; - const orderId = 4; + it('sets status to shipped when both fields provided and previous status pending', async () => { + const trackingCostDto: TrackingCostDto = { + trackingLink: 'testtracking.com', + shippingCost: 5.75, + }; + const orderId = 4; - const order = await service.findOne(orderId); + const order = await service.findOne(orderId); - expect(order.status).toEqual(OrderStatus.PENDING); - expect(order.shippedAt).toBeNull(); + expect(order.status).toEqual(OrderStatus.PENDING); + expect(order.shippedAt).toBeNull(); - await service.updateTrackingCostInfo(orderId, trackingCostDto); + await service.updateTrackingCostInfo(orderId, trackingCostDto); - const updatedOrder = await service.findOne(orderId); + const updatedOrder = await service.findOne(orderId); - expect(updatedOrder.status).toEqual(OrderStatus.SHIPPED); - expect(updatedOrder.shippedAt).toBeDefined(); + expect(updatedOrder.status).toEqual(OrderStatus.SHIPPED); + expect(updatedOrder.shippedAt).toBeDefined(); + }); }); }); }); diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index b7af3beb..f00af821 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 { @@ -77,6 +78,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'); @@ -111,7 +142,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..02c0b823 100644 --- a/apps/frontend/src/components/forms/orderDetailsModal.tsx +++ b/apps/frontend/src/components/forms/orderDetailsModal.tsx @@ -1,36 +1,46 @@ -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 +48,51 @@ 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{' '} - - - - - - Food Type(s) - - - - {foodRequest.requestedItems.map((item, index) => ( - + + + Request {foodRequest.requestId} - + + {' '} + {foodRequest.pantry?.pantryName} + + + {orderDetails?.status === OrderStatus.DELIVERED ? ( + + Closed + + ) : ( + + Active + + )} + + + + + Size of Shipment + + + + {foodRequest.requestedSize} + + + + + + + + Food Type(s) + + + + {foodRequest.requestedItems.length > 0 && ( + + {foodRequest.requestedItems.map((item) => ( + + {item} + + ))} + + )} + + + + + + Additional Information + + + + {foodRequest.additionalInformation} + + + + )} + + + + {Object.entries( + groupedOrderItemsByType as Record, + ).map(([foodType, items]) => ( + + {foodType} + {items.map((item) => ( + - {item} - + + {item.name} + + + + + + {item.quantity} + + ))} - - - - - - - Additional Information - - -