diff --git a/apps/frontend/src/app.spec.tsx b/apps/frontend/src/app.spec.tsx new file mode 100644 index 000000000..95caf44df --- /dev/null +++ b/apps/frontend/src/app.spec.tsx @@ -0,0 +1,15 @@ +import { render } from '@testing-library/react'; + +import App from './app'; + +describe('App', () => { + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); + + it('should have a greeting as the title', () => { + const { getByText } = render(); + expect(getByText(/Welcome frontend/gi)).toBeTruthy(); + }); +}); diff --git a/apps/frontend/src/components/ApplicationTable.tsx b/apps/frontend/src/components/ApplicationTable.tsx index 6a1d75cd7..535ade7a4 100644 --- a/apps/frontend/src/components/ApplicationTable.tsx +++ b/apps/frontend/src/components/ApplicationTable.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { Table } from '@chakra-ui/react'; const COLUMNS = [ @@ -63,7 +64,23 @@ const APPLICATIONS = [ }, ]; -export const ApplicationTable: React.FC = () => { +interface ApplicationTableProps { + searchQuery?: string; +} + +export function ApplicationTable({ searchQuery = '' }: ApplicationTableProps) { + const filteredApplications = APPLICATIONS.filter((application) => { + if (!searchQuery) return true; + const query = searchQuery.toLowerCase(); + return ( + application.name.toLowerCase().includes(query) || + application.discipline.toLowerCase().includes(query) || + application.disciplineAdminName.toLowerCase().includes(query) || + application.status.toLowerCase().includes(query) || + application.experienceType.toLowerCase().includes(query) + ); + }); + return ( @@ -80,7 +97,7 @@ export const ApplicationTable: React.FC = () => { - {APPLICATIONS.map((application) => ( + {filteredApplications.map((application) => ( {application.name} {application.proposedDate} @@ -94,6 +111,6 @@ export const ApplicationTable: React.FC = () => { ); -}; +} export default ApplicationTable; diff --git a/apps/frontend/src/components/ApprovedCard.tsx b/apps/frontend/src/components/ApprovedCard.tsx new file mode 100644 index 000000000..87d59bac7 --- /dev/null +++ b/apps/frontend/src/components/ApprovedCard.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { Box, Heading, Text, Flex } from '@chakra-ui/react'; + +interface ApprovedCardProps { + title: string; + count: number; + description: string; + icon: React.ReactNode; +} + +export function ApprovedCard({ + title, + count, + description, + icon, +}: ApprovedCardProps) { + return ( + + + + {title} + + + {icon} + + + + {count} + + + {description} + + + ); +} diff --git a/apps/frontend/src/components/PageCounter.tsx b/apps/frontend/src/components/PageCounter.tsx new file mode 100644 index 000000000..d5d84d27c --- /dev/null +++ b/apps/frontend/src/components/PageCounter.tsx @@ -0,0 +1,87 @@ +interface PageCounterProps { + page: number; + setPage: (page: number) => void; + maxPages: number; +} + +function PageCounter({ page, setPage, maxPages }: PageCounterProps) { + const getPageNumbers = () => { + const pages: (number | string)[] = []; + + if (maxPages <= 4) { + // Show all pages if 4 or fewer + for (let i = 1; i <= maxPages; i++) { + pages.push(i); + } + } else { + // Always show first 3 pages when near the start + if (page <= 3) { + pages.push(1, 2, 3); + pages.push('...'); + pages.push(maxPages); + } + // Show last 3 pages when near the end + else if (page >= maxPages - 2) { + pages.push(1); + pages.push('...'); + pages.push(maxPages - 2, maxPages - 1, maxPages); + } + // Show current page with neighbors in the middle + else { + pages.push(1); + pages.push('...'); + pages.push(page - 1, page, page + 1); + pages.push('...'); + pages.push(maxPages); + } + } + + return pages; + }; + + return ( +
+ {getPageNumbers().map((p, index) => + typeof p === 'string' ? ( + + {p} + + ) : ( + + ), + )} +
+ ); +} + +export default PageCounter; diff --git a/apps/frontend/src/components/PageTransitionButton.tsx b/apps/frontend/src/components/PageTransitionButton.tsx new file mode 100644 index 000000000..bc847112f --- /dev/null +++ b/apps/frontend/src/components/PageTransitionButton.tsx @@ -0,0 +1,41 @@ +import { IoChevronBack, IoChevronForward } from 'react-icons/io5'; + +function PageTransitionButton({ + buttonType, + onClick, +}: { + buttonType: 'previous' | 'next'; + onClick: () => void; +}) { + return ( + + ); +} + +export default PageTransitionButton; diff --git a/apps/frontend/src/components/TableSearchBar.tsx b/apps/frontend/src/components/TableSearchBar.tsx new file mode 100644 index 000000000..0bbe1e366 --- /dev/null +++ b/apps/frontend/src/components/TableSearchBar.tsx @@ -0,0 +1,52 @@ +import { IoSearch } from 'react-icons/io5'; + +interface SearchbarProps { + value: string; + onChange: (e: React.ChangeEvent) => void; +} + +function Searchbar({ value, onChange }: SearchbarProps) { + return ( +
+ + +
+ ); +} + +export default Searchbar; diff --git a/apps/frontend/src/containers/root.tsx b/apps/frontend/src/containers/root.tsx index 4f55def13..7bd4f80cb 100644 --- a/apps/frontend/src/containers/root.tsx +++ b/apps/frontend/src/containers/root.tsx @@ -6,14 +6,31 @@ import clockIcon from '../assets/icons/clock.svg'; import crossIcon from '../assets/icons/cross.svg'; import checkmarkIcon from '../assets/icons/checkmark.svg'; import { Box } from '@chakra-ui/react'; +import { useState } from 'react'; +import PageTransitionButton from '@components/PageTransitionButton'; +import Searchbar from '@components/TableSearchBar'; +import PageCounter from '@components/PageCounter'; import ApplicationTable from '@components/ApplicationTable'; const Root: React.FC = () => { + const [searchQuery, setSearchQuery] = useState(''); + const [page, setPage] = useState(1); + + function onChange(e: React.ChangeEvent) { + setSearchQuery(e.target.value); + } return ( -
+
- - + + { icon={checkmarkIcon} /> - +
+

Recent Applications

+
+ + + + + + +
+ setPage(page - 1)} + /> + + setPage(page + 1)} + /> +
);