Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/backend/src/orders/order.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -38,6 +39,7 @@ import { EmailsModule } from '../emails/email.module';
ManufacturerModule,
DonationItemsModule,
DonationModule,
forwardRef(() => PantriesModule),
EmailsModule,
],
controllers: [OrdersController],
Expand Down
70 changes: 70 additions & 0 deletions apps/frontend/src/components/PageEmptyState.tsx

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

our files are named in camelCase~ could you rename the empty state files accordingly?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like pageEmptyState.tsx @jxuistrying

Original file line number Diff line number Diff line change
@@ -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<PageEmptyStateProps> = ({
entity,
subtitle,
primaryButtonText,
primaryButtonLink,
secondaryButtonText,
secondaryButtonLink,
}) => {
const navigate = useNavigate();
const message = subtitle ?? `You have no ${entity} at this time`;

return (
<Box
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
textAlign="center"
py={10}
gap={2}
>
<Box mb={2}>
<CircleCheck size={24} color="var(--chakra-colors-neutral-800)" />
</Box>
<Text fontWeight="600" textStyle="p" color="neutral.800">
Nothing to see here!
</Text>
<Text textStyle="p2" color="neutral.700" fontWeight="400">
{message}
</Text>
<Box display="flex" gap={3} mt={4}>
<Button
size="sm"
bg="neutral.700"
Comment thread
Yurika-Kan marked this conversation as resolved.
color="white"
_hover={{ bg: 'neutral.800' }}
onClick={() => navigate(primaryButtonLink)}
>
{primaryButtonText}
</Button>
<Button
size="sm"
variant="outline"
borderColor="neutral.200"
color="neutral.700"
_hover={{ bg: 'neutral.50' }}
onClick={() => navigate(secondaryButtonLink)}
>
{secondaryButtonText}
</Button>
</Box>
</Box>
);
};

export default PageEmptyState;
30 changes: 30 additions & 0 deletions apps/frontend/src/components/SectionEmptyState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Box, Text } from '@chakra-ui/react';

interface EmptyStateProps {
entity?: string;
subtitle?: string;
}

const SectionEmptyState: React.FC<EmptyStateProps> = ({ entity, subtitle }) => {
const message = subtitle ?? `You have no ${entity} at this time`;
return (
<Box
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
textAlign="center"
py={10}
gap={2}
>
<Text fontWeight="600" textStyle="p" color="neutral.800">
Nothing to see here!
</Text>
<Text textStyle="p2" color="neutral.700" fontWeight="400">
{message}
</Text>
</Box>
);
};

export default SectionEmptyState;
208 changes: 132 additions & 76 deletions apps/frontend/src/containers/adminDashboard.tsx
Comment thread
Yurika-Kan marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -84,6 +86,11 @@ const AdminDashboard: React.FC = () => {
fetchPendingApplications();
}, [setAlertMessage]);

const isPageEmpty =

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isPageEmpty is computed from array lengths that start empty, so on first render when the page is still waiting for the data to be fetched, it shows empty state component instead. this is misleading to the user as they will think there are no ex. pantry applications to review vs the actual case that the data was being fetched.

i suggest implementing a loading flag to differentiate loading vs no data states.

for example, this pattern in
foodManufacturerApplicationDetails.tsxuses a loading flag useState(true) → setLoading(false) where if the page is loading, it returns null before the empty state branch. ie it will not show misleading empty state component.

const [loading, setLoading] = useState(true);

useEffect(() => {
  const load = async () => {
    try {
      await Promise.all([
        fetchMe(), fetchRecentDonations(),
        fetchRecentOrders(), fetchPendingApplications(),
      ]);
    } finally {
      setLoading(false);
    }
  };
  load();
}, [setAlertMessage]);

if (loading) return null;            // or a <Spinner/>, matching the app

lmk your thoughts on this~ if you test the pages too you can see what im referring to: flashes of the empty state component before data loads.

and can you apply that to the other three dashboards~

pendingApplications.length === 0 &&
recentOrders.length === 0 &&
recentDonations.length === 0;

return (
<Box p={12}>
{alertState && (
Expand All @@ -98,84 +105,133 @@ const AdminDashboard: React.FC = () => {
Welcome, {currentUser?.firstName} {currentUser?.lastName}
</Heading>

<Text textStyle="p" color="gray.light" fontWeight={600} mb={4}>
Pending Actions
</Text>
<Box display="grid" gridTemplateColumns="repeat(2, 1fr)" gap={4} mb={16}>
{pendingApplications.map((application) => (
<DashboardCard
type={DashboardCardType.ACTION}
title={application.name}
date={application.dateApplied}
key={application.id}
linkText="View Application Details"
badge={{
label:
application.type === 'pantry' ? 'Pantry' : 'Food Manufacturer',
bg: 'neutral.100',
color: 'neutral.600',
}}
onLinkClick={() => {
navigate(
application.type === 'pantry'
? ROUTES.PANTRY_MANAGEMENT_DETAILS.replace(
':pantryId',
application.id.toString(),
)
: ROUTES.FOOD_MANUFACTURER_APPLICATION_DETAILS.replace(
':applicationId',
application.id.toString(),
),
);
}}
/>
))}
</Box>
{isPageEmpty ? (
<PageEmptyState
entity="orders or applications to review"
primaryButtonText="View Pantries"
primaryButtonLink={ROUTES.PANTRY_MANAGEMENT}
secondaryButtonText="View Donations"
secondaryButtonLink={ROUTES.ADMIN_DONATION}
/>
) : (
<>
<Text textStyle="p" color="gray.light" fontWeight={600} mb={4}>
Pending Actions
</Text>
{pendingApplications.length === 0 ? (
<Box mb={16}>
<SectionEmptyState entity="pending applications" />
</Box>
) : (
<Box
display="grid"
gridTemplateColumns="repeat(2, 1fr)"
gap={4}
mb={16}
>
{pendingApplications.map((application) => (
<DashboardCard
type={DashboardCardType.ACTION}
title={application.name}
date={application.dateApplied}
key={application.id}
linkText="View Application Details"
badge={{
label:
application.type === 'pantry'
? 'Pantry'
: 'Food Manufacturer',
bg: 'neutral.100',
color: 'neutral.600',
}}
onLinkClick={() => {
navigate(
application.type === 'pantry'
? ROUTES.PANTRY_MANAGEMENT_DETAILS.replace(
':pantryId',
application.id.toString(),
)
: ROUTES.FOOD_MANUFACTURER_APPLICATION_DETAILS.replace(
':applicationId',
application.id.toString(),
),
);
}}
/>
))}
</Box>
)}

<Text textStyle="p" color="gray.light" fontWeight={600} mb={4}>
Recent Orders
</Text>
<Box display="grid" gridTemplateColumns="repeat(2, 1fr)" gap={4} mb={16}>
{recentOrders.map((order) => (
<DashboardCard
key={order.orderId}
type={DashboardCardType.ORDER}
title={`Order #${order.orderId}`}
date={order.createdAt}
subtitle={order.request.pantry.pantryName}
linkText="View Order Details"
badge={ORDER_STATUS_BADGE[order.status]}
assignee={{
id: order.assignee.id,
firstName: order.assignee.firstName,
lastName: order.assignee.lastName,
}}
onLinkClick={() =>
navigate(`/admin-order-management?orderId=${order.orderId}`)
}
/>
))}
</Box>
<Text textStyle="p" color="gray.light" fontWeight={600} mb={4}>
Recent Orders
</Text>
{recentOrders.length === 0 ? (
<Box mb={16}>
<SectionEmptyState entity="recent orders" />
</Box>
) : (
<Box
display="grid"
gridTemplateColumns="repeat(2, 1fr)"
gap={4}
mb={16}
>
{recentOrders.map((order) => (
<DashboardCard
key={order.orderId}
type={DashboardCardType.ORDER}
title={`Order #${order.orderId}`}
date={order.createdAt}
subtitle={order.request.pantry.pantryName}
linkText="View Order Details"
badge={ORDER_STATUS_BADGE[order.status]}
assignee={{
id: order.assignee.id,
firstName: order.assignee.firstName,
lastName: order.assignee.lastName,
}}
onLinkClick={() =>
navigate(`/admin-order-management?orderId=${order.orderId}`)
}
/>
))}
</Box>
)}

<Text textStyle="p" color="gray.light" fontWeight={600} mb={4}>
Recent Donations
</Text>
<Box display="grid" gridTemplateColumns="repeat(2, 1fr)" gap={4} mb={16}>
{recentDonations.map((donation) => (
<DashboardCard
key={donation.donationId}
type={DashboardCardType.RECENT_DONATION}
title={`Donation #${donation.donationId}`}
date={donation.dateDonated}
subtitle={donation.foodManufacturer?.foodManufacturerName}
linkText="View Donation Details"
badge={DONATION_STATUS_BADGE[donation.status]}
onLinkClick={() =>
navigate(`/admin-donation?donationId=${donation.donationId}`)
}
/>
))}
</Box>
<Text textStyle="p" color="gray.light" fontWeight={600} mb={4}>
Recent Donations
</Text>
{recentDonations.length === 0 ? (
<Box mb={16}>
<SectionEmptyState entity="recent donations" />
</Box>
) : (
<Box
display="grid"
gridTemplateColumns="repeat(2, 1fr)"
gap={4}
mb={16}
>
{recentDonations.map((donation) => (
<DashboardCard
key={donation.donationId}
type={DashboardCardType.RECENT_DONATION}
title={`Donation #${donation.donationId}`}
date={donation.dateDonated}
subtitle={donation.foodManufacturer?.foodManufacturerName}
linkText="View Donation Details"
badge={DONATION_STATUS_BADGE[donation.status]}
onLinkClick={() =>
navigate(
`/admin-donation?donationId=${donation.donationId}`,
)
}
/>
))}
</Box>
)}
</>
)}
</Box>
);
};
Expand Down
Loading
Loading