Skip to content

Commit

Permalink
(forgot to stage in last commit) added pagination feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Ryaken-Nakamoto committed Dec 5, 2024
1 parent dde66d9 commit 7718198
Show file tree
Hide file tree
Showing 9 changed files with 1,522 additions and 115 deletions.
1,281 changes: 1,215 additions & 66 deletions frontend/package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
"preview": "vite preview"
},
"dependencies": {
"@mui/material": "^6.1.10",
"@types/react-router-dom": "^5.3.3",
"chakra-pagination": "^1.0.3",
"core-js": "^3.38.1",
"mobx": "^6.13.2",
"react": "^18.3.1",
"react-app-polyfill": "^3.0.0",
"react-dom": "^18.3.1",
"react-icons": "^5.4.0",
"react-router-dom": "^6.26.2",
"satcheljs": "^4.3.1"
},
Expand All @@ -30,6 +33,6 @@
"globals": "^15.9.0",
"typescript": "^5.5.3",
"typescript-eslint": "^8.0.1",
"vite": "^5.4.8"
"vite": "^5.4.11"
}
}
12 changes: 12 additions & 0 deletions frontend/src/components/ui/link-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"use client"

import type { HTMLChakraProps, RecipeProps } from "@chakra-ui/react"
import { createRecipeContext } from "@chakra-ui/react"

export interface LinkButtonProps
extends HTMLChakraProps<"a", RecipeProps<"button">> {}

const { withContext } = createRecipeContext({ key: "button" })

// Replace "a" with your framework's link component
export const LinkButton = withContext<HTMLAnchorElement, LinkButtonProps>("a")
208 changes: 208 additions & 0 deletions frontend/src/components/ui/pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
"use client"

import type { ButtonProps, TextProps } from "@chakra-ui/react"
import {
Button,
Pagination as ChakraPagination,
IconButton,
Text,
createContext,
usePaginationContext,
} from "@chakra-ui/react"
import * as React from "react"
import {
HiChevronLeft,
HiChevronRight,
HiMiniEllipsisHorizontal,
} from "react-icons/hi2"
import { LinkButton } from "./link-button"

interface ButtonVariantMap {
current: ButtonProps["variant"]
default: ButtonProps["variant"]
ellipsis: ButtonProps["variant"]
}

type PaginationVariant = "outline" | "solid" | "subtle"

interface ButtonVariantContext {
size: ButtonProps["size"]
variantMap: ButtonVariantMap
getHref?: (page: number) => string
}

const [RootPropsProvider, useRootProps] = createContext<ButtonVariantContext>({
name: "RootPropsProvider",
})

export interface PaginationRootProps
extends Omit<ChakraPagination.RootProps, "type"> {
size?: ButtonProps["size"]
variant?: PaginationVariant
getHref?: (page: number) => string
}

const variantMap: Record<PaginationVariant, ButtonVariantMap> = {
outline: { default: "ghost", ellipsis: "plain", current: "outline" },
solid: { default: "outline", ellipsis: "outline", current: "solid" },
subtle: { default: "ghost", ellipsis: "plain", current: "subtle" },
}

export const PaginationRoot = React.forwardRef<
HTMLDivElement,
PaginationRootProps
>(function PaginationRoot(props, ref) {
const { size = "sm", variant = "outline", getHref, ...rest } = props
return (
<RootPropsProvider
value={{ size, variantMap: variantMap[variant], getHref }}
>
<ChakraPagination.Root
ref={ref}
type={getHref ? "link" : "button"}
{...rest}
/>
</RootPropsProvider>
)
})

export const PaginationEllipsis = React.forwardRef<
HTMLDivElement,
ChakraPagination.EllipsisProps
>(function PaginationEllipsis(props, ref) {
const { size, variantMap } = useRootProps()
return (
<ChakraPagination.Ellipsis ref={ref} {...props} asChild>
<Button as="span" variant={variantMap.ellipsis} size={size}>
<HiMiniEllipsisHorizontal />
</Button>
</ChakraPagination.Ellipsis>
)
})

export const PaginationItem = React.forwardRef<
HTMLButtonElement,
ChakraPagination.ItemProps
>(function PaginationItem(props, ref) {
const { page } = usePaginationContext()
const { size, variantMap, getHref } = useRootProps()

const current = page === props.value
const variant = current ? variantMap.current : variantMap.default

if (getHref) {
return (
<LinkButton href={getHref(props.value)} variant={variant} size={size}>
{props.value}
</LinkButton>
)
}

return (
<ChakraPagination.Item ref={ref} {...props} asChild>
<Button variant={variant} size={size}>
{props.value}
</Button>
</ChakraPagination.Item>
)
})

export const PaginationPrevTrigger = React.forwardRef<
HTMLButtonElement,
ChakraPagination.PrevTriggerProps
>(function PaginationPrevTrigger(props, ref) {
const { size, variantMap, getHref } = useRootProps()
const { previousPage } = usePaginationContext()

if (getHref) {
return (
<LinkButton
href={previousPage != null ? getHref(previousPage) : undefined}
variant={variantMap.default}
size={size}
>
<HiChevronLeft />
</LinkButton>
)
}

return (
<ChakraPagination.PrevTrigger ref={ref} asChild {...props}>
<IconButton variant={variantMap.default} size={size}>
<HiChevronLeft />
</IconButton>
</ChakraPagination.PrevTrigger>
)
})

export const PaginationNextTrigger = React.forwardRef<
HTMLButtonElement,
ChakraPagination.NextTriggerProps
>(function PaginationNextTrigger(props, ref) {
const { size, variantMap, getHref } = useRootProps()
const { nextPage } = usePaginationContext()

if (getHref) {
return (
<LinkButton
href={nextPage != null ? getHref(nextPage) : undefined}
variant={variantMap.default}
size={size}
>
<HiChevronRight />
</LinkButton>
)
}

return (
<ChakraPagination.NextTrigger ref={ref} asChild {...props}>
<IconButton variant={variantMap.default} size={size}>
<HiChevronRight />
</IconButton>
</ChakraPagination.NextTrigger>
)
})

export const PaginationItems = (props: React.HTMLAttributes<HTMLElement>) => {
return (
<ChakraPagination.Context>
{({ pages }) =>
pages.map((page, index) => {
return page.type === "ellipsis" ? (
<PaginationEllipsis key={index} index={index} {...props} />
) : (
<PaginationItem
key={index}
type="page"
value={page.value}
{...props}
/>
)
})
}
</ChakraPagination.Context>
)
}

interface PageTextProps extends TextProps {
format?: "short" | "compact" | "long"
}

export const PaginationPageText = React.forwardRef<
HTMLParagraphElement,
PageTextProps
>(function PaginationPageText(props, ref) {
const { format = "compact", ...rest } = props
const { page, totalPages, pageRange, count } = usePaginationContext()
const content = React.useMemo(() => {
if (format === "short") return `${page} / ${totalPages}`
if (format === "compact") return `${page} of ${totalPages}`
return `${pageRange.start + 1} - ${pageRange.end} of ${count}`
}, [format, page, totalPages, pageRange, count])

return (
<Text fontWeight="medium" ref={ref} {...rest}>
{content}
</Text>
)
})
55 changes: 55 additions & 0 deletions frontend/src/grant-info/components/CustomPagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { useState } from 'react';
import { Pagination } from '@mui/material';
import GrantItem, { GrantItemProps } from './GrantItem';

interface CustomPaginationProps {
grants: GrantItemProps[];
}

const grantsPerPage = 3;

const CustomPagination: React.FC<CustomPaginationProps> = ({ grants }) => {

const pageCount = Math.ceil(grants.length / grantsPerPage);

const [pagination, setPagination] = useState({
idxFrom: 0,
idxTo: grantsPerPage - 1
});

const handlePageChange = (_event: React.ChangeEvent<unknown>, page: number) => {
const idxFrom = (page - 1) * grantsPerPage;
const idxTo = Math.min(idxFrom + grantsPerPage - 1, grants.length-1); // make sure no out of bounds
setPagination({ idxFrom, idxTo });
};


console.log(`Current grant indices from: ${pagination.idxFrom} to ${pagination.idxTo}`);

return (
<div>
{/* render grants based on actual indices */}
{(() => {
const renderedGrants = [];
for (let i = pagination.idxFrom; i <= pagination.idxTo; i++) {
renderedGrants.push(
<GrantItem
key={i}
grantName={grants[i].grantName}
applicationDate={grants[i].applicationDate}
generalStatus={grants[i].generalStatus}
amount={grants[i].amount}
restrictionStatus={grants[i].restrictionStatus}
/>
);
}
return renderedGrants;
})()}

{/* pagination controls */}
<Pagination count={pageCount} onChange={handlePageChange}/>
</div>
);
};

export default CustomPagination;
4 changes: 0 additions & 4 deletions frontend/src/grant-info/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import React from 'react';
import Pagination from "./Pagination";
import './styles/Footer.css'

const Footer: React.FC = () => {
return (
<div className="footer">
<div className="pagination-wrapper">
<Pagination/>
</div>
<button className="add-grant-button">+</button>
</div>
)
Expand Down
32 changes: 3 additions & 29 deletions frontend/src/grant-info/components/GrantItem.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React, {useState} from 'react';
import './styles/GrantItem.css';

interface GrantItemProps {
export interface GrantItemProps {
grantName: string;
applicationDate: string;
generalStatus: string;
amount: number;
restrictionStatus: string;
}

const GrantItem: React.FC<GrantItemProps> = (props) => {
const { grantName, applicationDate, generalStatus, amount, restrictionStatus } = props;

Expand All @@ -32,35 +33,8 @@ const GrantItem: React.FC<GrantItemProps> = (props) => {
<div className="grant-description">
<h2>Grant Description:</h2>
<p>
The Community Development Initiative Grant is designed to empower
local organizations in implementing impactful projects that address
critical social and economic issues. This grant focuses on fostering
community-led programs that aim to improve educational
opportunities, enhance public health services, and support economic
development within underserved areas. Applicants are encouraged to
outline how their proposed projects will contribute to sustainable
growth, promote equity, and engage local stakeholders.
</p>
<p>
Eligible programs include those that offer job training and
workforce development, youth mentorship, health and wellness
programs, and initiatives aimed at reducing environmental impacts.
Each application should include a detailed plan that highlights the
project’s goals, implementation strategies, and measurable outcomes.
Projects that demonstrate strong community involvement and
partnerships with other local organizations will be prioritized for
funding.
(Grant Description)
</p>
<p>
Funding for this grant may cover program expenses such as staffing,
equipment, training materials, and outreach activities. The review
committee seeks innovative and sustainable approaches that align
with the mission of strengthening communities and fostering
long-term positive change. Grant recipients will also be expected to
submit periodic reports outlining the progress and achievements of
their projects over the funding period.
</p>

</div>
)}
</div>
Expand Down
Loading

0 comments on commit 7718198

Please sign in to comment.