diff --git a/apps/backend/src/app.module.ts b/apps/backend/src/app.module.ts index 8fc31d76..9eba2c0e 100644 --- a/apps/backend/src/app.module.ts +++ b/apps/backend/src/app.module.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; - +import { RequestsModule } from './foodRequests/request.module'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { UsersModule } from './users/users.module'; @@ -22,6 +22,7 @@ import typeorm from './config/typeorm'; }), UsersModule, AuthModule, + RequestsModule, ], controllers: [AppController], providers: [AppService], diff --git a/apps/backend/src/foodRequests/request.controller.ts b/apps/backend/src/foodRequests/request.controller.ts new file mode 100644 index 00000000..97bfe1ce --- /dev/null +++ b/apps/backend/src/foodRequests/request.controller.ts @@ -0,0 +1,87 @@ +import { + Controller, + Get, + Param, + ParseIntPipe, + Post, + Body, +} from '@nestjs/common'; +import { ApiBody } from '@nestjs/swagger'; +import { RequestsService } from './request.service'; +import { FoodRequest } from './request.entity'; + +@Controller('requests') +//@UseInterceptors() +export class FoodRequestsController { + constructor(private requestsService: RequestsService) {} + + @Get('/:pantryId') + async getAllPantryRequests( + @Param('pantryId', ParseIntPipe) pantryId: number, + ): Promise { + return this.requestsService.find(pantryId); + } + + @Post('/create') + @ApiBody({ + description: 'Details for creating a food request', + schema: { + type: 'object', + properties: { + pantryId: { type: 'integer', example: 1 }, + requestedSize: { type: 'string', example: 'Medium (5-10 boxes)' }, + requestedItems: { + type: 'array', + items: { type: 'string' }, + example: ['Rice Noodles', 'Quinoa'], + }, + additionalInformation: { + type: 'string', + nullable: true, + example: 'Urgent request', + }, + status: { type: 'string', example: 'pending' }, + fulfilledBy: { type: 'integer', nullable: true, example: null }, + dateReceived: { + type: 'string', + format: 'date-time', + nullable: true, + example: null, + }, + feedback: { type: 'string', nullable: true, example: null }, + photos: { + type: 'array', + items: { type: 'string' }, + nullable: true, + example: [], + }, + }, + }, + }) + async createRequest( + @Body() + body: { + pantryId: number; + requestedSize: string; + requestedItems: string[]; + additionalInformation: string; + status: string; + fulfilledBy: number; + dateReceived: Date; + feedback: string; + photos: string[]; + }, + ): Promise { + return this.requestsService.create( + body.pantryId, + body.requestedSize, + body.requestedItems, + body.additionalInformation, + body.status, + body.fulfilledBy, + body.dateReceived, + body.feedback, + body.photos, + ); + } +} diff --git a/apps/backend/src/foodRequests/request.entity.ts b/apps/backend/src/foodRequests/request.entity.ts new file mode 100644 index 00000000..46a77811 --- /dev/null +++ b/apps/backend/src/foodRequests/request.entity.ts @@ -0,0 +1,46 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, +} from 'typeorm'; + +@Entity('food_requests') +export class FoodRequest { + @PrimaryGeneratedColumn({ name: 'request_id' }) + requestId: number; + + @Column({ name: 'pantry_id', type: 'int' }) + pantryId: number; + + @Column({ name: 'requested_size', type: 'varchar', length: 50 }) + requestedSize: string; + + @Column({ name: 'requested_items', type: 'text', array: true }) + requestedItems: string[]; + + @Column({ name: 'additional_information', type: 'text', nullable: true }) + additionalInformation: string; + + @CreateDateColumn({ + name: 'requested_at', + type: 'timestamp', + default: () => 'NOW()', + }) + requestedAt: Date; + + @Column({ name: 'status', type: 'varchar', length: 25, default: 'pending' }) + status: string; + + @Column({ name: 'fulfilled_by', type: 'int', nullable: true }) + fulfilledBy: number; + + @Column({ name: 'date_received', type: 'timestamp', nullable: true }) + dateReceived: Date; + + @Column({ name: 'feedback', type: 'text', nullable: true }) + feedback: string; + + @Column({ name: 'photos', type: 'text', array: true, nullable: true }) + photos: string[]; +} diff --git a/apps/backend/src/foodRequests/request.module.ts b/apps/backend/src/foodRequests/request.module.ts new file mode 100644 index 00000000..7d55c32e --- /dev/null +++ b/apps/backend/src/foodRequests/request.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { FoodRequestsController } from './request.controller'; +import { FoodRequest } from './request.entity'; +import { RequestsService } from './request.service'; +import { JwtStrategy } from '../auth/jwt.strategy'; +import { AuthService } from '../auth/auth.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([FoodRequest])], + controllers: [FoodRequestsController], + providers: [RequestsService, AuthService, JwtStrategy], +}) +export class RequestsModule {} diff --git a/apps/backend/src/foodRequests/request.service.ts b/apps/backend/src/foodRequests/request.service.ts new file mode 100644 index 00000000..98812ca5 --- /dev/null +++ b/apps/backend/src/foodRequests/request.service.ts @@ -0,0 +1,45 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { FoodRequest } from './request.entity'; + +@Injectable() +export class RequestsService { + constructor( + @InjectRepository(FoodRequest) private repo: Repository, + ) {} + + async create( + pantryId: number, + requestedSize: string, + requestedItems: string[], + additionalInformation: string | null, + status: string = 'pending', + fulfilledBy: number | null, + dateReceived: Date | null, + feedback: string | null, + photos: string[] | null, + ) { + const foodRequest = this.repo.create({ + pantryId, + requestedSize, + requestedItems, + additionalInformation, + status, + fulfilledBy, + dateReceived, + feedback, + photos, + }); + + return this.repo.save(foodRequest); + } + + find(pantryId: number) { + if (!pantryId || pantryId < 1) { + throw new NotFoundException('Invalid pantry ID'); + } + return this.repo.find({ where: { pantryId } }); + } +} diff --git a/apps/frontend/src/app.tsx b/apps/frontend/src/app.tsx index 19db5939..e9491b1d 100644 --- a/apps/frontend/src/app.tsx +++ b/apps/frontend/src/app.tsx @@ -4,13 +4,12 @@ import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import apiClient from '@api/apiClient'; import Root from '@containers/root'; import NotFound from '@containers/404'; -import { submitFoodRequestForm } from '@components/forms/foodRequestForm'; -import RequestFood from '@containers/foodRequest'; import LandingPage from '@containers/landingPage'; import PantryOverview from '@containers/pantryOverview'; import PantryPastOrders from '@containers/pantryPastOrders'; import Pantries from '@containers/pantries'; import Orders from '@containers/orders'; +import { submitFoodRequestFormModal } from '@components/forms/requestFormModalButton'; const router = createBrowserRouter([ { @@ -38,13 +37,12 @@ const router = createBrowserRouter([ path: '/orders', element: , }, + { + path: '/food-request', // The route to handle form submission + action: submitFoodRequestFormModal, // Action function to handle the form data and redirection + }, ], }, - { - path: '/food-request', - element: , - action: submitFoodRequestForm, - }, ]); export const App: React.FC = () => { diff --git a/apps/frontend/src/components/forms/foodRequestForm.tsx b/apps/frontend/src/components/forms/foodRequestForm.tsx deleted file mode 100644 index adf4e514..00000000 --- a/apps/frontend/src/components/forms/foodRequestForm.tsx +++ /dev/null @@ -1,218 +0,0 @@ -import { - Flex, - Box, - Heading, - FormControl, - FormLabel, - Input, - Button, - FormHelperText, - Checkbox, - Stack, - Textarea, - SimpleGrid, - NumberInput, - NumberInputField, - NumberInputStepper, - NumberIncrementStepper, - NumberDecrementStepper, - CheckboxGroup, -} from '@chakra-ui/react'; -import { - Form, - redirect, - ActionFunction, - ActionFunctionArgs, -} from 'react-router-dom'; - -// should be an API call, dummy data for now -const getMenu = () => { - return { - Dairy: [ - 'Whole Milk', - 'Lactose-Free Milk', - 'Salted Butter', - 'Eggs', - 'Yogurt', - 'American Cheese', - 'Mozzarella Cheese', - ], - Meat: [ - 'Chicken Breast', - 'Ground Beef', - 'Ground Pork', - 'Ground Turkey', - 'Chicken Strips', - 'Turkey Bacon', - ], - Fruit: [ - 'Pear', - 'Orange', - 'Banana', - 'Lychee', - 'Tangerine', - 'Tomato', - 'Pineapple', - ], - Vegetables: ['Cauliflower', '小白菜', 'Spinach', 'Broccolini', 'Seaweed'], - Snacks: [ - 'Oreos (20ct)', - 'Potato Chips', - 'Chocolate Bars (4 ct)', - 'Nimbu Masala', - ], - Alcohol: [ - 'Red Wine', - 'Fireball', - 'Tequila', - 'Sake', - 'Malt Liquor', - 'Vodka', - 'Rum', - ], - }; -}; - -// might be an API call, dummy data for now -const getAllergens = () => { - return [ - 'Milk', - 'Eggs', - 'Fish', - 'Shellfish', - 'Tree Nuts', - 'Peanuts', - 'Wheat', - 'Soybeans', - 'Sesame', - 'Other (specify in notes)', - ]; -}; - -const FoodRequestForm: React.FC = () => { - const renderAllergens = () => { - return getAllergens().map((a) => ( - - {a} - - )); - }; - - const renderMenuSection = (sectionItems: Array) => { - return ( - - {sectionItems.map((x) => ( - - - - - - - - -

{x}

-
- ))} -
- ); - }; - - const renderMenu = () => { - const menu: Map> = new Map(Object.entries(getMenu())); - const menuSections: JSX.Element[] = []; - menu.forEach((v, k) => { - menuSections.push( -
- - {k} - - {renderMenuSection(v)} -
, - ); - }); - return menuSections; - }; - - return ( - -
- - SSF Food Request Form - - - - Requested Delivery Date - - - - We'll reach out to confirm a date and time - - - - - Dietary Restrictions - - - - {renderAllergens()} - - - - - - Requested Items - - {renderMenu()} - - - - Additional Comments - -