diff --git a/apps/backend/src/orders/order.module.ts b/apps/backend/src/orders/order.module.ts index 0e3367fb0..57bf8bc0e 100644 --- a/apps/backend/src/orders/order.module.ts +++ b/apps/backend/src/orders/order.module.ts @@ -17,6 +17,7 @@ import { ManufacturerModule } from '../foodManufacturers/manufacturers.module'; import { DonationItemsModule } from '../donationItems/donationItems.module'; import { Allocation } from '../allocations/allocations.entity'; import { Donation } from '../donations/donations.entity'; +import { PantriesModule } from '../pantries/pantries.module'; import { EmailsModule } from '../emails/email.module'; @Module({ @@ -38,6 +39,7 @@ import { EmailsModule } from '../emails/email.module'; ManufacturerModule, DonationItemsModule, DonationModule, + forwardRef(() => PantriesModule), EmailsModule, ], controllers: [OrdersController], diff --git a/apps/frontend/src/components/PageEmptyState.tsx b/apps/frontend/src/components/PageEmptyState.tsx new file mode 100644 index 000000000..a6476f4b5 --- /dev/null +++ b/apps/frontend/src/components/PageEmptyState.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { Box, Button, Text } from '@chakra-ui/react'; +import { CircleCheck } from 'lucide-react'; +import { useNavigate } from 'react-router-dom'; + +interface PageEmptyStateProps { + entity?: string; + subtitle?: string; + primaryButtonText: string; + primaryButtonLink: string; + secondaryButtonText: string; + secondaryButtonLink: string; +} + +const PageEmptyState: React.FC = ({ + entity, + subtitle, + primaryButtonText, + primaryButtonLink, + secondaryButtonText, + secondaryButtonLink, +}) => { + const navigate = useNavigate(); + const message = subtitle ?? `You have no ${entity} at this time`; + + return ( + + + + + + Nothing to see here! + + + {message} + + + + + + + ); +}; + +export default PageEmptyState; diff --git a/apps/frontend/src/components/SectionEmptyState.tsx b/apps/frontend/src/components/SectionEmptyState.tsx new file mode 100644 index 000000000..96a91773c --- /dev/null +++ b/apps/frontend/src/components/SectionEmptyState.tsx @@ -0,0 +1,30 @@ +import { Box, Text } from '@chakra-ui/react'; + +interface EmptyStateProps { + entity?: string; + subtitle?: string; +} + +const SectionEmptyState: React.FC = ({ entity, subtitle }) => { + const message = subtitle ?? `You have no ${entity} at this time`; + return ( + + + Nothing to see here! + + + {message} + + + ); +}; + +export default SectionEmptyState; diff --git a/apps/frontend/src/containers/adminDashboard.tsx b/apps/frontend/src/containers/adminDashboard.tsx index 7ce01914e..628f97a0a 100644 --- a/apps/frontend/src/containers/adminDashboard.tsx +++ b/apps/frontend/src/containers/adminDashboard.tsx @@ -16,6 +16,8 @@ import { useAlert } from '../hooks/alert'; import { FloatingAlert } from '@components/floatingAlert'; import { useNavigate } from 'react-router-dom'; import { ROUTES } from '../routes'; +import SectionEmptyState from '@components/sectionEmptyState'; +import PageEmptyState from '@components/pageEmptyState'; const AdminDashboard: React.FC = () => { const navigate = useNavigate(); @@ -84,6 +86,11 @@ const AdminDashboard: React.FC = () => { fetchPendingApplications(); }, [setAlertMessage]); + const isPageEmpty = + pendingApplications.length === 0 && + recentOrders.length === 0 && + recentDonations.length === 0; + return ( {alertState && ( @@ -98,84 +105,133 @@ const AdminDashboard: React.FC = () => { Welcome, {currentUser?.firstName} {currentUser?.lastName} - - Pending Actions - - - {pendingApplications.map((application) => ( - { - navigate( - application.type === 'pantry' - ? ROUTES.PANTRY_MANAGEMENT_DETAILS.replace( - ':pantryId', - application.id.toString(), - ) - : ROUTES.FOOD_MANUFACTURER_APPLICATION_DETAILS.replace( - ':applicationId', - application.id.toString(), - ), - ); - }} - /> - ))} - + {isPageEmpty ? ( + + ) : ( + <> + + Pending Actions + + {pendingApplications.length === 0 ? ( + + + + ) : ( + + {pendingApplications.map((application) => ( + { + navigate( + application.type === 'pantry' + ? ROUTES.PANTRY_MANAGEMENT_DETAILS.replace( + ':pantryId', + application.id.toString(), + ) + : ROUTES.FOOD_MANUFACTURER_APPLICATION_DETAILS.replace( + ':applicationId', + application.id.toString(), + ), + ); + }} + /> + ))} + + )} - - Recent Orders - - - {recentOrders.map((order) => ( - - navigate(`/admin-order-management?orderId=${order.orderId}`) - } - /> - ))} - + + Recent Orders + + {recentOrders.length === 0 ? ( + + + + ) : ( + + {recentOrders.map((order) => ( + + navigate(`/admin-order-management?orderId=${order.orderId}`) + } + /> + ))} + + )} - - Recent Donations - - - {recentDonations.map((donation) => ( - - navigate(`/admin-donation?donationId=${donation.donationId}`) - } - /> - ))} - + + Recent Donations + + {recentDonations.length === 0 ? ( + + + + ) : ( + + {recentDonations.map((donation) => ( + + navigate( + `/admin-donation?donationId=${donation.donationId}`, + ) + } + /> + ))} + + )} + + )} ); }; diff --git a/apps/frontend/src/containers/foodManufacturerDashboard.tsx b/apps/frontend/src/containers/foodManufacturerDashboard.tsx index 93dd02e0d..3c6ee0c2f 100644 --- a/apps/frontend/src/containers/foodManufacturerDashboard.tsx +++ b/apps/frontend/src/containers/foodManufacturerDashboard.tsx @@ -1,17 +1,13 @@ import React, { useEffect, useState } from 'react'; import { Box, Heading, Text } from '@chakra-ui/react'; import DashboardCard, { DashboardCardType } from '@components/dashboardCard'; -import { - Donation, - DonationDetails, - DonationReminderDto, - FoodManufacturer, -} from '../types/types'; +import { Donation, DonationDetails, FoodManufacturer } from '../types/types'; import ApiClient from '@api/apiClient'; import { useAlert } from '../hooks/alert'; import { FloatingAlert } from '@components/floatingAlert'; import { useNavigate } from 'react-router-dom'; import { ROUTES } from '../routes'; +import SectionEmptyState from '@components/sectionEmptyState'; const FoodManufacturerDashboard: React.FC = () => { const navigate = useNavigate(); @@ -19,9 +15,6 @@ const FoodManufacturerDashboard: React.FC = () => { const [errorAlertState, setErrorMessage] = useAlert(); const [foodManufacturer, setFoodManufacturer] = useState(null); - const [upcomingReminders, setUpcomingReminders] = useState< - DonationReminderDto[] - >([]); const [recentDonations, setRecentDonations] = useState([]); useEffect(() => { @@ -36,21 +29,11 @@ const FoodManufacturerDashboard: React.FC = () => { return; } - const [reminders, donations] = await Promise.allSettled([ - ApiClient.getNextTwoDonationReminders(fmId), - ApiClient.getAllDonationsByFoodManufacturer(fmId), - ]); - - // If reminders is successfully retrieved from API with the Promise.allSettled - if (reminders.status === 'fulfilled') { - setUpcomingReminders(reminders.value); - } else { - setErrorMessage('Error fetching upcoming donations.'); - } - - // If donations is successfully retrieved from API with the Promise.allSettled - if (donations.status === 'fulfilled') { - const sorted = donations.value + try { + const donations = await ApiClient.getAllDonationsByFoodManufacturer( + fmId, + ); + const sorted = donations .map((d: DonationDetails) => d.donation) .sort( (a: Donation, b: Donation) => @@ -59,13 +42,15 @@ const FoodManufacturerDashboard: React.FC = () => { ) .slice(0, 2); setRecentDonations(sorted); - } else { + } catch { setErrorMessage('Error fetching recent donations.'); } }; fetchFmData(); }, [setErrorMessage]); + if (!foodManufacturer) return null; + return ( {errorAlertState && ( @@ -80,47 +65,35 @@ const FoodManufacturerDashboard: React.FC = () => { Welcome, {foodManufacturer?.foodManufacturerName} - - Upcoming Donations - - - {upcomingReminders.map((reminder) => ( - - navigate( - `${ROUTES.FM_DONATION_MANAGEMENT}?donationId=${reminder.donation.donationId}`, - ) - } - /> - ))} - - Recent Donations - - {recentDonations.map((donation) => ( - - navigate( - `${ROUTES.FM_DONATION_MANAGEMENT}?donationId=${donation.donationId}`, - ) - } - /> - ))} - + {recentDonations.length === 0 ? ( + + ) : ( + + {recentDonations.map((donation) => ( + + navigate( + `${ROUTES.FM_DONATION_MANAGEMENT}?donationId=${donation.donationId}`, + ) + } + /> + ))} + + )} ); }; diff --git a/apps/frontend/src/containers/foodManufacturerDonationManagement.tsx b/apps/frontend/src/containers/foodManufacturerDonationManagement.tsx index 8005e28eb..95a7b5ce3 100644 --- a/apps/frontend/src/containers/foodManufacturerDonationManagement.tsx +++ b/apps/frontend/src/containers/foodManufacturerDonationManagement.tsx @@ -10,7 +10,7 @@ import { ButtonGroup, Link, } from '@chakra-ui/react'; -import { ChevronRight, ChevronLeft, Mail, CircleCheck } from 'lucide-react'; +import { ChevronRight, ChevronLeft, Mail } from 'lucide-react'; import { capitalize, formatDate, DONATION_STATUS_COLORS } from '@utils/utils'; import ApiClient from '@api/apiClient'; import { DonationDetails, DonationStatus } from '../types/types'; @@ -22,6 +22,7 @@ import { FloatingAlert } from '@components/floatingAlert'; import { useAlert } from '../hooks/alert'; import DonationDetailsModal from '@components/forms/donationDetailsModal'; import FmCompleteRequiredActionsModal from '@components/forms/fmCompleteRequiredActionsModal'; +import SectionEmptyState from '@components/sectionEmptyState'; const MAX_PER_STATUS = 5; @@ -364,28 +365,7 @@ const DonationStatusSection: React.FC = ({ {donations.length === 0 ? ( - - - - - - No Donations - - - You have no {status.toLowerCase()} donations at this time. - - + ) : ( <> diff --git a/apps/frontend/src/containers/pantryDashboard.tsx b/apps/frontend/src/containers/pantryDashboard.tsx index 182c9a9ef..98204fba2 100644 --- a/apps/frontend/src/containers/pantryDashboard.tsx +++ b/apps/frontend/src/containers/pantryDashboard.tsx @@ -12,6 +12,8 @@ import { useAlert } from '../hooks/alert'; import { FloatingAlert } from '@components/floatingAlert'; import { useNavigate } from 'react-router-dom'; import { ROUTES } from '../routes'; +import SectionEmptyState from '@components/sectionEmptyState'; +import PageEmptyState from '@components/pageEmptyState'; const PantryDashboard: React.FC = () => { const navigate = useNavigate(); @@ -63,6 +65,9 @@ const PantryDashboard: React.FC = () => { if (!pantry) return null; + const isPageEmpty = + recentFoodRequests.length === 0 && recentOrders.length === 0; + return ( {alertState && ( @@ -77,51 +82,85 @@ const PantryDashboard: React.FC = () => { Welcome, {pantry.pantryName} - - Recent Food Requests - - - {recentFoodRequests.map((fr) => ( - - navigate(`${ROUTES.REQUEST_FORM}?requestId=${fr.requestId}`) - } - /> - ))} - + {isPageEmpty ? ( + + ) : ( + <> + + Recent Food Requests + + {recentFoodRequests.length === 0 ? ( + + + + ) : ( + + {recentFoodRequests.map((fr) => ( + + navigate(`${ROUTES.REQUEST_FORM}?requestId=${fr.requestId}`) + } + /> + ))} + + )} - - Recent Orders - - - {recentOrders.map((order) => ( - - navigate( - `${ROUTES.PANTRY_ORDER_MANAGEMENT}?orderId=${order.orderId}`, - ) - } - /> - ))} - + + Recent Orders + + {recentOrders.length === 0 ? ( + + + + ) : ( + + {recentOrders.map((order) => ( + + navigate( + `${ROUTES.PANTRY_ORDER_MANAGEMENT}?orderId=${order.orderId}`, + ) + } + /> + ))} + + )} + + )} ); }; diff --git a/apps/frontend/src/containers/volunteerDashboard.tsx b/apps/frontend/src/containers/volunteerDashboard.tsx index c33843670..2e43a4bba 100644 --- a/apps/frontend/src/containers/volunteerDashboard.tsx +++ b/apps/frontend/src/containers/volunteerDashboard.tsx @@ -8,6 +8,8 @@ import { useAlert } from '../hooks/alert'; import { FloatingAlert } from '@components/floatingAlert'; import { useNavigate } from 'react-router-dom'; import { ROUTES } from '../routes'; +import SectionEmptyState from '@components/sectionEmptyState'; +import PageEmptyState from '@components/pageEmptyState'; const VolunteerDashboard: React.FC = () => { const navigate = useNavigate(); @@ -53,6 +55,9 @@ const VolunteerDashboard: React.FC = () => { if (!user) return null; + const isPageEmpty = + recentFoodRequests.length === 0 && recentOrders.length === 0; + return ( {alertState && ( @@ -67,53 +72,87 @@ const VolunteerDashboard: React.FC = () => { Welcome, {user.firstName} {user.lastName} - - Recent Food Requests - - - {recentFoodRequests.map((fr) => ( - - navigate( - `${ROUTES.VOLUNTEER_REQUEST_MANAGEMENT}?requestId=${fr.requestId}`, - ) - } - /> - ))} - + {isPageEmpty ? ( + + ) : ( + <> + + Recent Food Requests + + {recentFoodRequests.length === 0 ? ( + + + + ) : ( + + {recentFoodRequests.map((fr) => ( + + navigate( + `${ROUTES.VOLUNTEER_REQUEST_MANAGEMENT}?requestId=${fr.requestId}`, + ) + } + /> + ))} + + )} - - My Orders - - - {recentOrders.map((order) => ( - - navigate( - `${ROUTES.VOLUNTEER_ORDER_MANAGEMENT}?orderId=${order.orderId}`, - ) - } - /> - ))} - + + My Orders + + {recentOrders.length === 0 ? ( + + + + ) : ( + + {recentOrders.map((order) => ( + + navigate( + `${ROUTES.VOLUNTEER_ORDER_MANAGEMENT}?orderId=${order.orderId}`, + ) + } + /> + ))} + + )} + + )} ); };