diff --git a/apps/backend/src/auth/auth.controller.ts b/apps/backend/src/auth/auth.controller.ts index fd16bd9b..a1382845 100644 --- a/apps/backend/src/auth/auth.controller.ts +++ b/apps/backend/src/auth/auth.controller.ts @@ -6,7 +6,7 @@ import { AuthService } from './auth.service'; import { UsersService } from '../users/users.service'; import { VerifyUserDto } from './dtos/verify-user.dto'; import { DeleteUserDto } from './dtos/delete-user.dto'; -import { User } from '../users/user.entity'; +import { User } from '../users/users.entity'; import { SignInResponseDto } from './dtos/sign-in-response.dto'; import { RefreshTokenDto } from './dtos/refresh-token.dto'; import { ConfirmPasswordDto } from './dtos/confirm-password.dto'; diff --git a/apps/backend/src/auth/authenticated-request.ts b/apps/backend/src/auth/authenticated-request.ts index d8c0673f..fc803c98 100644 --- a/apps/backend/src/auth/authenticated-request.ts +++ b/apps/backend/src/auth/authenticated-request.ts @@ -1,5 +1,5 @@ import { Request } from 'express'; -import { User } from '../users/user.entity'; +import { User } from '../users/users.entity'; // user does not have to be provided by the client but is added automatically by the auth backend export interface AuthenticatedRequest extends Request { diff --git a/apps/backend/src/auth/jwt.strategy.ts b/apps/backend/src/auth/jwt.strategy.ts index 1c28aaf9..951be9de 100644 --- a/apps/backend/src/auth/jwt.strategy.ts +++ b/apps/backend/src/auth/jwt.strategy.ts @@ -5,7 +5,7 @@ import { ExtractJwt, Strategy } from 'passport-jwt'; import { UsersService } from '../users/users.service'; import CognitoAuthConfig from './aws-exports'; import { CognitoJwtPayload } from './jwt-payload.interface'; -import { User } from '../users/user.entity'; +import { User } from '../users/users.entity'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { diff --git a/apps/backend/src/config/typeormTestDataSource.ts b/apps/backend/src/config/typeormTestDataSource.ts index cfbc089e..d1c0bd92 100644 --- a/apps/backend/src/config/typeormTestDataSource.ts +++ b/apps/backend/src/config/typeormTestDataSource.ts @@ -3,7 +3,7 @@ import { DataSource, DataSourceOptions } from 'typeorm'; import { PluralNamingStrategy } from '../strategies/plural-naming.strategy'; import { Order } from '../orders/order.entity'; import { Pantry } from '../pantries/pantries.entity'; -import { User } from '../users/user.entity'; +import { User } from '../users/users.entity'; import { Donation } from '../donations/donations.entity'; import { FoodManufacturer } from '../foodManufacturers/manufacturers.entity'; import { FoodRequest } from '../foodRequests/request.entity'; diff --git a/apps/backend/src/foodManufacturers/manufacturers.entity.ts b/apps/backend/src/foodManufacturers/manufacturers.entity.ts index 8d351052..3cff3e05 100644 --- a/apps/backend/src/foodManufacturers/manufacturers.entity.ts +++ b/apps/backend/src/foodManufacturers/manufacturers.entity.ts @@ -6,7 +6,7 @@ import { OneToMany, JoinColumn, } from 'typeorm'; -import { User } from '../users/user.entity'; +import { User } from '../users/users.entity'; import { Donation } from '../donations/donations.entity'; import { Allergen, DonateWastedFood, ManufacturerAttribute } from './types'; import { ApplicationStatus } from '../shared/types'; diff --git a/apps/backend/src/foodManufacturers/manufacturers.service.ts b/apps/backend/src/foodManufacturers/manufacturers.service.ts index 73a3a12d..ee4b49ac 100644 --- a/apps/backend/src/foodManufacturers/manufacturers.service.ts +++ b/apps/backend/src/foodManufacturers/manufacturers.service.ts @@ -4,7 +4,7 @@ import { FoodManufacturer } from './manufacturers.entity'; import { Repository } from 'typeorm'; import { validateId } from '../utils/validation.utils'; import { FoodManufacturerApplicationDto } from './dtos/manufacturer-application.dto'; -import { User } from '../users/user.entity'; +import { User } from '../users/users.entity'; import { Role } from '../users/types'; import { ApplicationStatus } from '../shared/types'; import { userSchemaDto } from '../users/dtos/userSchema.dto'; diff --git a/apps/backend/src/pantries/pantries.controller.spec.ts b/apps/backend/src/pantries/pantries.controller.spec.ts index 4ca9f2d4..256151aa 100644 --- a/apps/backend/src/pantries/pantries.controller.spec.ts +++ b/apps/backend/src/pantries/pantries.controller.spec.ts @@ -16,7 +16,8 @@ import { } from './types'; import { EmailsService } from '../emails/email.service'; import { ApplicationStatus } from '../shared/types'; -import { User } from '../users/user.entity'; +import { NotFoundException, UnauthorizedException } from '@nestjs/common'; +import { User } from '../users/users.entity'; import { AuthenticatedRequest } from '../auth/authenticated-request'; const mockPantriesService = mock(); diff --git a/apps/backend/src/pantries/pantries.entity.ts b/apps/backend/src/pantries/pantries.entity.ts index 395f14a0..5760acfc 100644 --- a/apps/backend/src/pantries/pantries.entity.ts +++ b/apps/backend/src/pantries/pantries.entity.ts @@ -6,7 +6,7 @@ import { JoinColumn, ManyToMany, } from 'typeorm'; -import { User } from '../users/user.entity'; +import { User } from '../users/users.entity'; import { Activity, AllergensConfidence, diff --git a/apps/backend/src/pantries/pantries.module.ts b/apps/backend/src/pantries/pantries.module.ts index 76a192fe..64b5ca7c 100644 --- a/apps/backend/src/pantries/pantries.module.ts +++ b/apps/backend/src/pantries/pantries.module.ts @@ -6,7 +6,7 @@ import { Pantry } from './pantries.entity'; import { AuthModule } from '../auth/auth.module'; import { OrdersModule } from '../orders/order.module'; import { EmailsModule } from '../emails/email.module'; -import { User } from '../users/user.entity'; +import { User } from '../users/users.entity'; import { UsersModule } from '../users/users.module'; @Module({ diff --git a/apps/backend/src/pantries/pantries.service.spec.ts b/apps/backend/src/pantries/pantries.service.spec.ts index 22e0a9ad..aad4cf6a 100644 --- a/apps/backend/src/pantries/pantries.service.spec.ts +++ b/apps/backend/src/pantries/pantries.service.spec.ts @@ -16,7 +16,7 @@ import { } from './types'; import { ApplicationStatus } from '../shared/types'; import { UsersService } from '../users/users.service'; -import { User } from '../users/user.entity'; +import { User } from '../users/users.entity'; import { Role } from '../users/types'; const mockRepository = mock>(); diff --git a/apps/backend/src/pantries/pantries.service.ts b/apps/backend/src/pantries/pantries.service.ts index 9c7cffe3..d21cd516 100644 --- a/apps/backend/src/pantries/pantries.service.ts +++ b/apps/backend/src/pantries/pantries.service.ts @@ -7,7 +7,7 @@ import { import { InjectRepository } from '@nestjs/typeorm'; import { In, Repository } from 'typeorm'; import { Pantry } from './pantries.entity'; -import { User } from '../users/user.entity'; +import { User } from '../users/users.entity'; import { validateId } from '../utils/validation.utils'; import { ApplicationStatus } from '../shared/types'; import { PantryApplicationDto } from './dtos/pantry-application.dto'; diff --git a/apps/backend/src/users/users.controller.spec.ts b/apps/backend/src/users/users.controller.spec.ts index 58182c73..0a2b0433 100644 --- a/apps/backend/src/users/users.controller.spec.ts +++ b/apps/backend/src/users/users.controller.spec.ts @@ -1,12 +1,13 @@ import { BadRequestException } from '@nestjs/common'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; -import { User } from './user.entity'; +import { User } from './users.entity'; import { Role } from './types'; import { userSchemaDto } from './dtos/userSchema.dto'; import { Test, TestingModule } from '@nestjs/testing'; import { mock } from 'jest-mock-extended'; +import { AuthenticatedRequest } from '../auth/authenticated-request'; const mockUserService = mock(); @@ -46,6 +47,16 @@ describe('UsersController', () => { expect(controller).toBeDefined(); }); + describe('GET /my-id', () => { + it('should return the current user id', () => { + const req = { user: { id: 1 } } as AuthenticatedRequest; + + const result = controller.getCurrentUserId(req); + + expect(result).toBe(1); + }); + }); + describe('GET /:id', () => { it('should return a user by id', async () => { mockUserService.findOne.mockResolvedValue(mockUser1 as User); diff --git a/apps/backend/src/users/users.controller.ts b/apps/backend/src/users/users.controller.ts index 064b688a..1a9963e5 100644 --- a/apps/backend/src/users/users.controller.ts +++ b/apps/backend/src/users/users.controller.ts @@ -8,16 +8,26 @@ import { Post, BadRequestException, Body, + Req, } from '@nestjs/common'; import { UsersService } from './users.service'; -import { User } from './user.entity'; +import { User } from './users.entity'; import { Role } from './types'; import { userSchemaDto } from './dtos/userSchema.dto'; +import { AuthenticatedRequest } from '../auth/authenticated-request'; +import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +import { UseGuards } from '@nestjs/common'; @Controller('users') export class UsersController { constructor(private usersService: UsersService) {} + @UseGuards(JwtAuthGuard) + @Get('/my-id') + getCurrentUserId(@Req() req: AuthenticatedRequest): number { + return req.user.id; + } + @Get('/:id') async getUser(@Param('id', ParseIntPipe) userId: number): Promise { return this.usersService.findOne(userId); diff --git a/apps/backend/src/users/user.entity.ts b/apps/backend/src/users/users.entity.ts similarity index 100% rename from apps/backend/src/users/user.entity.ts rename to apps/backend/src/users/users.entity.ts diff --git a/apps/backend/src/users/users.module.ts b/apps/backend/src/users/users.module.ts index 23177621..f7bf1c19 100644 --- a/apps/backend/src/users/users.module.ts +++ b/apps/backend/src/users/users.module.ts @@ -2,7 +2,7 @@ import { forwardRef, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; -import { User } from './user.entity'; +import { User } from './users.entity'; import { PantriesModule } from '../pantries/pantries.module'; import { AuthModule } from '../auth/auth.module'; diff --git a/apps/backend/src/users/users.service.spec.ts b/apps/backend/src/users/users.service.spec.ts index 43a6fa8c..4207622e 100644 --- a/apps/backend/src/users/users.service.spec.ts +++ b/apps/backend/src/users/users.service.spec.ts @@ -3,7 +3,7 @@ import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { UsersService } from './users.service'; -import { User } from './user.entity'; +import { User } from './users.entity'; import { Role } from './types'; import { mock } from 'jest-mock-extended'; import { In } from 'typeorm'; diff --git a/apps/backend/src/users/users.service.ts b/apps/backend/src/users/users.service.ts index fa3e008e..0377a616 100644 --- a/apps/backend/src/users/users.service.ts +++ b/apps/backend/src/users/users.service.ts @@ -1,7 +1,7 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { In, Repository } from 'typeorm'; -import { User } from './user.entity'; +import { User } from './users.entity'; import { Role } from './types'; import { validateId } from '../utils/validation.utils'; import { AuthService } from '../auth/auth.service'; diff --git a/apps/backend/src/volunteers/types.ts b/apps/backend/src/volunteers/types.ts new file mode 100644 index 00000000..0c167b2b --- /dev/null +++ b/apps/backend/src/volunteers/types.ts @@ -0,0 +1,3 @@ +import { User } from '../users/users.entity'; + +export type Assignments = Omit & { pantryIds: number[] }; diff --git a/apps/backend/src/volunteers/volunteers.controller.spec.ts b/apps/backend/src/volunteers/volunteers.controller.spec.ts index 5a50f637..8d4a1788 100644 --- a/apps/backend/src/volunteers/volunteers.controller.spec.ts +++ b/apps/backend/src/volunteers/volunteers.controller.spec.ts @@ -2,7 +2,7 @@ import { BadRequestException, NotFoundException } from '@nestjs/common'; import { VolunteersController } from './volunteers.controller'; import { UsersController } from '../users/users.controller'; import { UsersService } from '../users/users.service'; -import { User } from '../users/user.entity'; +import { User } from '../users/users.entity'; import { Role } from '../users/types'; import { Test, TestingModule } from '@nestjs/testing'; import { mock } from 'jest-mock-extended'; diff --git a/apps/backend/src/volunteers/volunteers.controller.ts b/apps/backend/src/volunteers/volunteers.controller.ts index ce6b9d62..29763de8 100644 --- a/apps/backend/src/volunteers/volunteers.controller.ts +++ b/apps/backend/src/volunteers/volunteers.controller.ts @@ -6,26 +6,23 @@ import { Post, Body, } from '@nestjs/common'; -import { User } from '../users/user.entity'; +import { User } from '../users/users.entity'; import { Pantry } from '../pantries/pantries.entity'; import { VolunteersService } from './volunteers.service'; +import { Role } from '../users/types'; +import { Roles } from '../auth/roles.decorator'; +import { Assignments } from './types'; @Controller('volunteers') export class VolunteersController { constructor(private volunteersService: VolunteersService) {} + @Roles(Role.ADMIN) @Get('/') - async getAllVolunteers(): Promise< - (Omit & { pantryIds: number[] })[] - > { + async getAllVolunteers(): Promise { return this.volunteersService.getVolunteersAndPantryAssignments(); } - @Get('/:id') - async getVolunteer(@Param('id', ParseIntPipe) userId: number): Promise { - return this.volunteersService.findOne(userId); - } - @Get('/:id/pantries') async getVolunteerPantries( @Param('id', ParseIntPipe) id: number, @@ -33,6 +30,11 @@ export class VolunteersController { return this.volunteersService.getVolunteerPantries(id); } + @Get('/:id') + async getVolunteer(@Param('id', ParseIntPipe) userId: number): Promise { + return this.volunteersService.findOne(userId); + } + @Post('/:id/pantries') async assignPantries( @Param('id', ParseIntPipe) id: number, diff --git a/apps/backend/src/volunteers/volunteers.module.ts b/apps/backend/src/volunteers/volunteers.module.ts index c7147fcf..7f7a5d89 100644 --- a/apps/backend/src/volunteers/volunteers.module.ts +++ b/apps/backend/src/volunteers/volunteers.module.ts @@ -1,6 +1,6 @@ import { forwardRef, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { User } from '../users/user.entity'; +import { User } from '../users/users.entity'; import { PantriesModule } from '../pantries/pantries.module'; import { AuthModule } from '../auth/auth.module'; import { VolunteersController } from './volunteers.controller'; diff --git a/apps/backend/src/volunteers/volunteers.service.spec.ts b/apps/backend/src/volunteers/volunteers.service.spec.ts index fb8f343b..1bbcc753 100644 --- a/apps/backend/src/volunteers/volunteers.service.spec.ts +++ b/apps/backend/src/volunteers/volunteers.service.spec.ts @@ -1,7 +1,7 @@ import { NotFoundException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { User } from '../users/user.entity'; +import { User } from '../users/users.entity'; import { VolunteersService } from './volunteers.service'; import { Pantry } from '../pantries/pantries.entity'; import { testDataSource } from '../config/typeormTestDataSource'; diff --git a/apps/backend/src/volunteers/volunteers.service.ts b/apps/backend/src/volunteers/volunteers.service.ts index ba6c5a10..32429296 100644 --- a/apps/backend/src/volunteers/volunteers.service.ts +++ b/apps/backend/src/volunteers/volunteers.service.ts @@ -1,12 +1,13 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { User } from '../users/user.entity'; +import { User } from '../users/users.entity'; import { Role } from '../users/types'; import { validateId } from '../utils/validation.utils'; import { Pantry } from '../pantries/pantries.entity'; import { PantriesService } from '../pantries/pantries.service'; import { UsersService } from '../users/users.service'; +import { Assignments } from './types'; @Injectable() export class VolunteersService { @@ -34,9 +35,7 @@ export class VolunteersService { return volunteer; } - async getVolunteersAndPantryAssignments(): Promise< - (Omit & { pantryIds: number[] })[] - > { + async getVolunteersAndPantryAssignments(): Promise { const volunteers = await this.usersService.findUsersByRoles([ Role.VOLUNTEER, ]); @@ -45,7 +44,7 @@ export class VolunteersService { const { pantries, ...volunteerWithoutPantries } = v; return { ...volunteerWithoutPantries, - pantryIds: pantries!.map((p) => p.pantryId), + pantryIds: pantries?.map((p) => p.pantryId) || [], }; }); } @@ -53,7 +52,7 @@ export class VolunteersService { async getVolunteerPantries(volunteerId: number): Promise { validateId(volunteerId, 'Volunteer'); const volunteer = await this.findOne(volunteerId); - return volunteer.pantries!; + return volunteer.pantries || []; } async assignPantriesToVolunteer( @@ -65,7 +64,7 @@ export class VolunteersService { const volunteer = await this.findOne(volunteerId); const pantries = await this.pantriesService.findByIds(pantryIds); - const existingPantries = volunteer.pantries!; + const existingPantries = volunteer.pantries || []; const existingPantryIds = existingPantries.map((p) => p.pantryId); const newPantries = pantries.filter( (p) => !existingPantryIds.includes(p.pantryId), diff --git a/apps/frontend/src/api/apiClient.ts b/apps/frontend/src/api/apiClient.ts index a22e2cd5..899e4ca8 100644 --- a/apps/frontend/src/api/apiClient.ts +++ b/apps/frontend/src/api/apiClient.ts @@ -21,7 +21,7 @@ import { OrderSummary, UserDto, OrderDetails, - FoodRequestSummaryDto, + Assignments, } from 'types/types'; const defaultBaseUrl = @@ -176,8 +176,12 @@ export class ApiClient { .then((response) => response.data); } - public async getVolunteers(): Promise { - return this.get('/api/volunteers/') as Promise; + public async getVolunteers(): Promise { + return this.get('/api/volunteers') as Promise; + } + + public async getVolunteerPantries(userId: number): Promise { + return this.get(`/api/volunteers/${userId}/pantries`) as Promise; } public async updateUserVolunteerRole( @@ -304,6 +308,11 @@ export class ApiClient { const data = await this.get('/api/pantries/my-id'); return data as number; } + + public async getMyId(): Promise { + const data = await this.get('/api/users/my-id'); + return data as number; + } } export default new ApiClient(); diff --git a/apps/frontend/src/app.tsx b/apps/frontend/src/app.tsx index 3c6631d3..ecedbf43 100644 --- a/apps/frontend/src/app.tsx +++ b/apps/frontend/src/app.tsx @@ -27,6 +27,7 @@ import Unauthorized from '@containers/unauthorized'; import { Authenticator } from '@aws-amplify/ui-react'; import FoodManufacturerApplication from '@containers/foodManufacturerApplication'; import { submitManufacturerApplicationForm } from '@components/forms/manufacturerApplicationForm'; +import AssignedPantries from '@containers/volunteerAssignedPantries'; Amplify.configure(CognitoAuthConfig); @@ -145,40 +146,48 @@ const router = createBrowserRouter([ ), }, { - path: '/approve-pantries', + path: '/volunteer-management', element: ( - + ), }, { - path: '/admin-donation', + path: '/admin-order-management', element: ( - + ), }, { - path: '/volunteer-management', + path: '/confirm-delivery', + action: submitDeliveryConfirmationFormModal, + }, + { + path: '/approve-pantries', element: ( - + ), }, { - path: '/admin-order-management', + path: '/admin-donation', element: ( - + ), }, { - path: '/confirm-delivery', - action: submitDeliveryConfirmationFormModal, + path: '/volunteer-assigned-pantries', + element: ( + + + + ), }, ], }, diff --git a/apps/frontend/src/containers/homepage.tsx b/apps/frontend/src/containers/homepage.tsx index 043cfcd9..d37eee02 100644 --- a/apps/frontend/src/containers/homepage.tsx +++ b/apps/frontend/src/containers/homepage.tsx @@ -87,6 +87,21 @@ const Homepage: React.FC = () => { + + + Volunteer View + + + + + + Assigned Pantries + + + + + + Admin View diff --git a/apps/frontend/src/containers/volunteerAssignedPantries.tsx b/apps/frontend/src/containers/volunteerAssignedPantries.tsx new file mode 100644 index 00000000..a8931895 --- /dev/null +++ b/apps/frontend/src/containers/volunteerAssignedPantries.tsx @@ -0,0 +1,393 @@ +import React, { useState, useEffect, useMemo } from 'react'; +import { Funnel, CircleCheck } from 'lucide-react'; +import { + Box, + Button, + Table, + Heading, + VStack, + Checkbox, + Text, + Spinner, + Input, +} from '@chakra-ui/react'; +import ApiClient from '@api/apiClient'; +import { Pantry } from 'types/types'; +import { RefrigeratedDonation } from '../types/pantryEnums'; +import { FloatingAlert } from '@components/floatingAlert'; +import { useNavigate } from 'react-router-dom'; + +const AssignedPantries: React.FC = () => { + const navigate = useNavigate(); + const [pantries, setPantries] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [isFilterOpen, setIsFilterOpen] = useState(false); + const [selectedPantryIds, setSelectedPantryIds] = useState>( + new Set(), + ); + const [pantrySearch, setPantrySearch] = useState(''); + const [alertMessage, setAlertMessage] = useState(null); + + useEffect(() => { + const fetchAssignedPantries = async () => { + let userId: number; + try { + userId = await ApiClient.getMyId(); + } catch { + setAlertMessage('Authentication error. Please log in and try again.'); + setIsLoading(false); + return; + } + + try { + const data = await ApiClient.getVolunteerPantries(userId); + setPantries(data); + } catch { + setAlertMessage('Error fetching assigned pantries'); + } finally { + setIsLoading(false); + } + }; + + fetchAssignedPantries(); + }, []); + + const isRefrigeratorFriendly = (pantry: Pantry): boolean => { + return ( + pantry.refrigeratedDonation === RefrigeratedDonation.YES || + pantry.refrigeratedDonation === RefrigeratedDonation.SOMETIMES + ); + }; + + const filteredPantries = useMemo(() => { + if (selectedPantryIds.size === 0) return pantries; + return pantries.filter((pantry) => selectedPantryIds.has(pantry.pantryId)); + }, [selectedPantryIds, pantries]); + + const hasNoAssignedPantries = !isLoading && pantries.length === 0; + const hasNoFilterResults = + !isLoading && pantries.length > 0 && filteredPantries.length === 0; + + const isFiltered = selectedPantryIds.size > 0; + + const togglePantry = (pantryId: number) => { + setSelectedPantryIds((prev) => { + const next = new Set(prev); + if (next.has(pantryId)) next.delete(pantryId); + else next.add(pantryId); + return next; + }); + }; + + const allChecked = + pantries.length > 0 && selectedPantryIds.size === pantries.length; + const isIndeterminate = selectedPantryIds.size > 0 && !allChecked; + + const toggleAll = () => { + if (allChecked || isIndeterminate) { + setSelectedPantryIds(new Set()); + } else { + setSelectedPantryIds(new Set(pantries.map((p) => p.pantryId))); + } + }; + + const visiblePantries = useMemo(() => { + if (!pantrySearch.trim()) return pantries; + return pantries.filter((p) => + p.pantryName.toLowerCase().includes(pantrySearch.toLowerCase()), + ); + }, [pantrySearch, pantries]); + + const tableHeaderStyles = { + borderBottom: '1px solid', + borderColor: 'neutral.100', + color: 'neutral.800', + fontFamily: 'inter', + fontWeight: '600', + fontSize: 'sm', + py: 3, + px: 4, + }; + + return ( + + {alertMessage && ( + + )} + + + Assigned Pantries + + + {isLoading ? ( + + + + ) : ( + <> + {!hasNoAssignedPantries && ( + + + + + {isFilterOpen && ( + <> + setIsFilterOpen(false)} + zIndex={10} + /> + + + setPantrySearch(e.target.value)} + fontFamily="inter" + /> + + + + + + + Select All + + + + + + + {visiblePantries.length > 0 ? ( + visiblePantries.map((pantry) => ( + + togglePantry(pantry.pantryId) + } + size="sm" + > + + + + + {pantry.pantryName} + + + + )) + ) : ( + + No pantries found. + + )} + + + + )} + + + )} + + {/* Empty States */} + {(hasNoAssignedPantries || hasNoFilterResults) && ( + + + {hasNoAssignedPantries ? ( + <> + + No Assigned Pantries + + + You have no assigned pantries at this time. + + + ) : ( + <> + + No Matching Pantries + + + No pantries match the current filter. + + + )} + + )} + + {/* Pantries Table */} + {filteredPantries.length > 0 && ( + + + + + Pantry + + + Refrigerator-Friendly + + + Action + + + + + {filteredPantries.map((pantry) => ( + + {/* Pantry Name */} + + + {pantry.pantryName} + + + + {/* Refrigerator-Friendly Badge */} + + + + {isRefrigeratorFriendly(pantry) + ? 'Refrigerator-Friendly' + : 'Not Refrigerator-Friendly'} + + + + + {/* Action */} + + + + + ))} + + + )} + + )} + + ); +}; + +export default AssignedPantries; diff --git a/apps/frontend/src/types/types.ts b/apps/frontend/src/types/types.ts index 5698abd0..7f28b9d9 100644 --- a/apps/frontend/src/types/types.ts +++ b/apps/frontend/src/types/types.ts @@ -361,4 +361,6 @@ export type DayOfWeek = export type RepeatOnState = Record; +export type Assignments = Omit & { pantryIds: number[] }; + export type GroupedByFoodType = Partial>;