From ca87a2a94d9a5ce462bee5f29034be14e1dba2e4 Mon Sep 17 00:00:00 2001 From: Poorvi Date: Sun, 11 Jan 2026 21:59:01 -0500 Subject: [PATCH 01/17] feat/add admin navbar and stat card component --- app/admin/const.ts | 22 ++++++++++++++++++++++ app/admin/page.tsx | 25 +++++++++++++++++++++++++ app/fonts.ts | 11 +++++++++++ components/AdminNavbar.tsx | 37 +++++++++++++++++++++++++++++++++++++ components/StatCard.tsx | 17 +++++++++++++++++ 5 files changed, 112 insertions(+) create mode 100644 app/admin/const.ts create mode 100644 app/fonts.ts create mode 100644 components/AdminNavbar.tsx create mode 100644 components/StatCard.tsx diff --git a/app/admin/const.ts b/app/admin/const.ts new file mode 100644 index 0000000..8ff85a3 --- /dev/null +++ b/app/admin/const.ts @@ -0,0 +1,22 @@ +export const ADMIN_DASHBOARD_CONST = { + PAGE_TITLE: 'Admin Dashboard', +}; + +export const ADMIN_DASHBOARD_MOCK = { + ADMIN_NAME: 'John Doe', + PROFILE_PICTURE_URL: undefined, + STAT_CARDS: [ + { + label: 'Independent Members', + value: 30, + }, + { + label: 'Organization Members', + value: 20, + }, + { + label: 'New Applications Submitted', + value: 6, + }, + ], +}; diff --git a/app/admin/page.tsx b/app/admin/page.tsx index e69de29..953406b 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -0,0 +1,25 @@ +import { inter } from '@/app/fonts'; +import AdminNavbar from '@/components/AdminNavbar'; +import StatCard from '@/components/StatCard'; +import { ADMIN_DASHBOARD_MOCK, ADMIN_DASHBOARD_CONST } from './const'; + +export default function AdminDashboard() { + return ( + <> + +
+ {ADMIN_DASHBOARD_CONST.PAGE_TITLE} +
+
+ {ADMIN_DASHBOARD_MOCK.STAT_CARDS.map((card) => ( + + ))} +
+ + ); +} diff --git a/app/fonts.ts b/app/fonts.ts new file mode 100644 index 0000000..9e9f732 --- /dev/null +++ b/app/fonts.ts @@ -0,0 +1,11 @@ +import { Roboto_Condensed, Inter } from 'next/font/google'; + +export const robotoCondensed = Roboto_Condensed({ + variable: '--font-roboto-condensed', + subsets: ['latin'], +}); + +export const inter = Inter({ + variable: '--font-inter', + subsets: ['latin'], +}); diff --git a/components/AdminNavbar.tsx b/components/AdminNavbar.tsx new file mode 100644 index 0000000..91a8263 --- /dev/null +++ b/components/AdminNavbar.tsx @@ -0,0 +1,37 @@ +import { robotoCondensed, inter } from '@/app/fonts'; + +type AdminNavbarProps = { + adminName: string; + profilePictureUrl?: string; +}; + +export default function AdminNavbar({ + adminName = 'Admin Name', + profilePictureUrl, +}: AdminNavbarProps) { + return ( +
+
+

+ Richmond Poverty
+ Reduction Coalition +

+
+

+ {adminName} +

+ {profilePictureUrl ? ( + // eslint-disable-next-line @next/next/no-img-element + + ) : ( +
+ )} +
+
+
+ ); +} diff --git a/components/StatCard.tsx b/components/StatCard.tsx new file mode 100644 index 0000000..8a6c3b1 --- /dev/null +++ b/components/StatCard.tsx @@ -0,0 +1,17 @@ +import { inter } from '@/app/fonts'; + +type StatCardProps = { + label: string; + value: number; +}; + +export default function StatCard({ label, value }: StatCardProps) { + return ( +
+

{value}

+

{label}

+
+ ); +} From a90cf32aea31d986fe15ea950126ae62d280b76d Mon Sep 17 00:00:00 2001 From: Poorvi Date: Sun, 11 Jan 2026 22:42:00 -0500 Subject: [PATCH 02/17] feat: add tabs component --- app/admin/const.ts | 22 ++++++++++++++++++++++ app/admin/page.tsx | 21 +++++++++++++++++---- components/Tabs.tsx | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 components/Tabs.tsx diff --git a/app/admin/const.ts b/app/admin/const.ts index 8ff85a3..2a792de 100644 --- a/app/admin/const.ts +++ b/app/admin/const.ts @@ -1,5 +1,27 @@ export const ADMIN_DASHBOARD_CONST = { PAGE_TITLE: 'Admin Dashboard', + TABS: [ + { + label: 'All', + value: 'all', + }, + { + label: 'To Review', + value: 'toReview', + }, + { + label: 'Payment Pending', + value: 'paymentPending', + }, + { + label: 'Completed', + value: 'completed', + }, + { + label: 'Rejected', + value: 'rejected', + }, + ], }; export const ADMIN_DASHBOARD_MOCK = { diff --git a/app/admin/page.tsx b/app/admin/page.tsx index 953406b..98f0adf 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -1,9 +1,15 @@ +'use client'; + import { inter } from '@/app/fonts'; import AdminNavbar from '@/components/AdminNavbar'; import StatCard from '@/components/StatCard'; +import Tabs from '@/components/Tabs'; import { ADMIN_DASHBOARD_MOCK, ADMIN_DASHBOARD_CONST } from './const'; +import { useState } from 'react'; export default function AdminDashboard() { + const [currentTab, setCurrentTab] = useState(ADMIN_DASHBOARD_CONST.TABS[0]); + return ( <> {ADMIN_DASHBOARD_CONST.PAGE_TITLE}
-
- {ADMIN_DASHBOARD_MOCK.STAT_CARDS.map((card) => ( - - ))} +
+
+ {ADMIN_DASHBOARD_MOCK.STAT_CARDS.map((card) => ( + + ))} +
+
); diff --git a/components/Tabs.tsx b/components/Tabs.tsx new file mode 100644 index 0000000..574f643 --- /dev/null +++ b/components/Tabs.tsx @@ -0,0 +1,35 @@ +import { inter } from '@/app/fonts'; + +type Tab = { + label: string; + value: string; +}; + +type TabsProps = { + tabs: Tab[]; + currentTab: Tab; + setCurrentTab: (tab: Tab) => void; +}; + +export default function Tabs({ tabs, currentTab, setCurrentTab }: TabsProps) { + return ( +
+ {tabs.map((tab) => { + const isActive = tab.value === currentTab.value; + + return ( + + ); + })} +
+ ); +} From e5935057fe88ea1b493d4ef5c567950f4d20c753 Mon Sep 17 00:00:00 2001 From: Poorvi Date: Sun, 11 Jan 2026 23:07:43 -0500 Subject: [PATCH 03/17] feat: status chip component --- components/StatusChip.tsx | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 components/StatusChip.tsx diff --git a/components/StatusChip.tsx b/components/StatusChip.tsx new file mode 100644 index 0000000..0aae0b3 --- /dev/null +++ b/components/StatusChip.tsx @@ -0,0 +1,37 @@ +import { inter } from '@/app/fonts'; + +type StatusChipType = { + color: string; + label: string; + theme: string; +}; + +const STATUS_CHIPS: StatusChipType[] = [ + { color: '#00519F', label: 'To Review', theme: 'toReview' }, + { color: '#393533', label: 'Rejected', theme: 'rejected' }, + { color: '#C67D38', label: 'Payment Pending', theme: 'paymentPending' }, + { color: '#3FA104', label: 'Complete', theme: 'complete' }, +]; + +type StatusChipProps = { + theme: 'toReview' | 'rejected' | 'paymentPending' | 'complete'; +}; + +export default function StatusChip({ theme }: StatusChipProps) { + const status = STATUS_CHIPS.find((s) => s.theme === theme); + + if (!status) return null; + + return ( +
+
+ +

+ {status.label} +

+
+ ); +} From 28dacff54b1a474257df428908a785bc5e8001fb Mon Sep 17 00:00:00 2001 From: Poorvi Date: Tue, 13 Jan 2026 22:13:45 -0500 Subject: [PATCH 04/17] fix: add additionalClasses prop --- components/Tabs.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/components/Tabs.tsx b/components/Tabs.tsx index 574f643..af7a030 100644 --- a/components/Tabs.tsx +++ b/components/Tabs.tsx @@ -9,11 +9,21 @@ type TabsProps = { tabs: Tab[]; currentTab: Tab; setCurrentTab: (tab: Tab) => void; + additionalClasses?: { + wrapper: string; + }; }; -export default function Tabs({ tabs, currentTab, setCurrentTab }: TabsProps) { +export default function Tabs({ + tabs, + currentTab, + setCurrentTab, + additionalClasses, +}: TabsProps) { return ( -
+
{tabs.map((tab) => { const isActive = tab.value === currentTab.value; From e3d03d676b619a4510f424b20e18ad4016de54b9 Mon Sep 17 00:00:00 2001 From: Poorvi Date: Tue, 13 Jan 2026 22:14:13 -0500 Subject: [PATCH 05/17] fix: update sizing of navbar for responsiveness --- components/AdminNavbar.tsx | 40 ++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/components/AdminNavbar.tsx b/components/AdminNavbar.tsx index 91a8263..1293dbd 100644 --- a/components/AdminNavbar.tsx +++ b/components/AdminNavbar.tsx @@ -6,27 +6,51 @@ type AdminNavbarProps = { }; export default function AdminNavbar({ - adminName = 'Admin Name', + adminName, profilePictureUrl, }: AdminNavbarProps) { return ( -
-
+
+

Richmond Poverty
Reduction Coalition

-
+ +

{adminName}

+ {profilePictureUrl ? ( - // eslint-disable-next-line @next/next/no-img-element - + ) : (
)} From a2de95eaf56f6915a1e24cfaa664291b6d6f9ff7 Mon Sep 17 00:00:00 2001 From: Poorvi Date: Thu, 15 Jan 2026 21:33:34 -0500 Subject: [PATCH 06/17] fix: update responsiveness and move table to own component --- app/admin/const.ts | 57 +++++++++++++++++++++++++++ app/admin/page.tsx | 16 ++++++-- components/AdminDashboardTable.tsx | 46 ++++++++++++++++++++++ components/AdminNavbar.tsx | 63 ++++++++++++++---------------- components/StatCard.tsx | 10 +++-- components/Table.tsx | 53 +++++++++++++++++++++++++ components/Tabs.tsx | 8 ++-- lib/utils.ts | 30 ++++++++++++-- types/adminDashboard.ts | 20 ++++++++++ 9 files changed, 255 insertions(+), 48 deletions(-) create mode 100644 components/AdminDashboardTable.tsx create mode 100644 components/Table.tsx create mode 100644 types/adminDashboard.ts diff --git a/app/admin/const.ts b/app/admin/const.ts index 2a792de..6a27bf0 100644 --- a/app/admin/const.ts +++ b/app/admin/const.ts @@ -1,3 +1,5 @@ +import { ApplicationType } from '@/types/adminDashboard'; + export const ADMIN_DASHBOARD_CONST = { PAGE_TITLE: 'Admin Dashboard', TABS: [ @@ -22,6 +24,14 @@ export const ADMIN_DASHBOARD_CONST = { value: 'rejected', }, ], + TABLE_COLUMNS: [ + { label: 'Applicant Name', value: 'applicantName', width: 'w-[260px]' }, + { label: 'Application Type', value: 'type', width: 'w-[200px]' }, + { label: 'Date Received', value: 'dateReceived', width: 'w-[200px]' }, + { label: 'Status', value: 'status', width: 'w-[280px]' }, + { label: 'Reviewer 1', value: 'reviewer1', width: 'w-[150px]' }, + { label: 'Reviewer 2', value: 'reviewer2', width: 'w-[150px]' }, + ], }; export const ADMIN_DASHBOARD_MOCK = { @@ -41,4 +51,51 @@ export const ADMIN_DASHBOARD_MOCK = { value: 6, }, ], + APPLICATIONS_MOCK: [ + { + id: 'APP01', + applicantName: 'Leighton Kramer', + type: 'Individual', + dateReceived: '2026-01-06', + status: 'toReview', + reviewer1: '', + reviewer2: '', + }, + { + id: 'APP02', + applicantName: 'Marceline Avila', + type: 'Organization', + dateReceived: '2025-12-18', + status: 'toReview', + reviewer1: 'Jane Doe', + reviewer2: '', + }, + { + id: 'APP03', + applicantName: 'Jensen Vang', + type: 'Organization', + dateReceived: '2025-11-11', + status: 'rejected', + reviewer1: 'John Doe', + reviewer2: 'Jane Doe', + }, + { + id: 'APP04', + applicantName: 'Linda Wu', + type: 'Individual', + dateReceived: '2025-11-11', + status: 'paymentPending', + reviewer1: 'John Doe', + reviewer2: 'Jane Doe', + }, + { + id: 'APP05', + applicantName: 'Carter Higgins', + type: 'Organization', + dateReceived: '2025-11-05', + status: 'complete', + reviewer1: 'John Doe', + reviewer2: 'Jane Doe', + }, + ] as ApplicationType[], }; diff --git a/app/admin/page.tsx b/app/admin/page.tsx index 98f0adf..5959af7 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -1,11 +1,12 @@ 'use client'; +import { useState } from 'react'; import { inter } from '@/app/fonts'; import AdminNavbar from '@/components/AdminNavbar'; import StatCard from '@/components/StatCard'; import Tabs from '@/components/Tabs'; import { ADMIN_DASHBOARD_MOCK, ADMIN_DASHBOARD_CONST } from './const'; -import { useState } from 'react'; +import AdminDashboardTable from '@/components/AdminDashboardTable'; export default function AdminDashboard() { const [currentTab, setCurrentTab] = useState(ADMIN_DASHBOARD_CONST.TABS[0]); @@ -17,12 +18,12 @@ export default function AdminDashboard() { profilePictureUrl={ADMIN_DASHBOARD_MOCK.PROFILE_PICTURE_URL} />
{ADMIN_DASHBOARD_CONST.PAGE_TITLE}
-
-
+
+
{ADMIN_DASHBOARD_MOCK.STAT_CARDS.map((card) => ( ))} @@ -31,7 +32,14 @@ export default function AdminDashboard() { tabs={ADMIN_DASHBOARD_CONST.TABS} currentTab={currentTab} setCurrentTab={setCurrentTab} + additionalClasses={{ + wrapper: 'ml:7 mt-10 md:mt-16 lg:mt-20 relative left-0', + }} /> + {/* */}
); diff --git a/components/AdminDashboardTable.tsx b/components/AdminDashboardTable.tsx new file mode 100644 index 0000000..462f423 --- /dev/null +++ b/components/AdminDashboardTable.tsx @@ -0,0 +1,46 @@ +import Table from './Table'; +import { formatDateWithOrdinal } from '@/lib/utils'; +import StatusChip from './StatusChip'; +import { AdminDashboardTablePropTypes } from '@/types/adminDashboard'; + +export default function AdminDashboardTable({ + columns, + applications, +}: AdminDashboardTablePropTypes) { + return ( + + {applications.map((app) => ( + + + + + + + + + + + + + + ))} +
+ {app.applicantName} + + {app.type} + + {formatDateWithOrdinal(app.dateReceived)} + + + + {app.reviewer1} + + {app.reviewer2} +
+ ); +} diff --git a/components/AdminNavbar.tsx b/components/AdminNavbar.tsx index 1293dbd..9aafe34 100644 --- a/components/AdminNavbar.tsx +++ b/components/AdminNavbar.tsx @@ -10,51 +10,46 @@ export default function AdminNavbar({ profilePictureUrl, }: AdminNavbarProps) { return ( -
-
-

+

- Richmond Poverty
- Reduction Coalition -

+ > + Richmond Poverty
+ Reduction Coalition +

-
-

+

- {adminName} -

+ > + {adminName} +

- {profilePictureUrl ? ( - - ) : ( -
- )} -
+ {profilePictureUrl ? ( + + ) : ( +
+ )}
); diff --git a/components/StatCard.tsx b/components/StatCard.tsx index 8a6c3b1..4ed9594 100644 --- a/components/StatCard.tsx +++ b/components/StatCard.tsx @@ -8,10 +8,14 @@ type StatCardProps = { export default function StatCard({ label, value }: StatCardProps) { return (
-

{value}

-

{label}

+

+ {value} +

+

+ {label} +

); } diff --git a/components/Table.tsx b/components/Table.tsx new file mode 100644 index 0000000..6017fea --- /dev/null +++ b/components/Table.tsx @@ -0,0 +1,53 @@ +import { inter } from '@/app/fonts'; +import { ReactNode } from 'react'; +import { ColumnType } from '@/types/adminDashboard'; + +type TableProps = { + columnNames: ColumnType[]; + sort?: { + sortedBy?: string; + sortingOrder: 'asc' | 'desc'; + }; + pagination?: { + itemCount: number; + numberPerPage: number; + usePagination?: boolean; + }; + children: ReactNode; + additionalClasses?: { + wrapper?: string; + table?: string; + }; +}; + +export default function Table({ + columnNames, + children, + additionalClasses, +}: TableProps) { + return ( +
+ + + + {columnNames.map((column, index) => ( + + ))} + + + + {children} +
+ {column.label} +
+
+ ); +} diff --git a/components/Tabs.tsx b/components/Tabs.tsx index af7a030..851e060 100644 --- a/components/Tabs.tsx +++ b/components/Tabs.tsx @@ -22,7 +22,7 @@ export default function Tabs({ }: TabsProps) { return (
{tabs.map((tab) => { const isActive = tab.value === currentTab.value; @@ -32,9 +32,9 @@ export default function Tabs({ key={tab.value} onClick={() => setCurrentTab(tab)} className={` - cursor-pointer font-medium text-[16px] leading-[160%] tracking-[-0.03em] underline underline-offset-[3px] - ${isActive ? 'inline-flex items-center px-6 py-2 bg-[#00519F54] rounded-[30px] border-4 border-[#00519F]' : ''} - `} + cursor-pointer font-medium text-[12px] md:text-[14px] lg:text-[16px] leading-[160%] tracking-[-0.03em] underline underline-offset-[3px] + ${isActive ? 'inline-flex items-center px-2 md:px-6 py-2 bg-[#00519F54] rounded-[30px] border-4 border-[#00519F]' : ''} + `} > {tab.label} diff --git a/lib/utils.ts b/lib/utils.ts index bd0c391..c7ca753 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,6 +1,30 @@ -import { clsx, type ClassValue } from "clsx" -import { twMerge } from "tailwind-merge" +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) + return twMerge(clsx(inputs)); +} + +function getOrdinal(day: number) { + if (day > 3 && day < 21) return 'th'; + switch (day % 10) { + case 1: + return 'st'; + case 2: + return 'nd'; + case 3: + return 'rd'; + default: + return 'th'; + } +} + +export function formatDateWithOrdinal(date: string | Date) { + const d = new Date(date); + + const day = d.getDate(); + const month = d.toLocaleString('en-US', { month: 'short' }); + const year = d.getFullYear(); + + return `${day}${getOrdinal(day)} ${month} ${year}`; } diff --git a/types/adminDashboard.ts b/types/adminDashboard.ts new file mode 100644 index 0000000..6a34b33 --- /dev/null +++ b/types/adminDashboard.ts @@ -0,0 +1,20 @@ +export type ColumnType = { + label: string; + value: string; + width?: string; +}; + +export type ApplicationType = { + id: string; + applicantName: string; + type: 'Individual' | 'Organization'; + dateReceived: string | Date; + status: 'toReview' | 'rejected' | 'paymentPending' | 'complete'; + reviewer1: string; + reviewer2: string; +}; + +export type AdminDashboardTablePropTypes = { + columns: ColumnType[]; + applications: ApplicationType[]; +}; From bc72f83a44ecc63f921dd8f2f23d28ed8862cf0f Mon Sep 17 00:00:00 2001 From: Poorvi Date: Thu, 15 Jan 2026 22:22:14 -0500 Subject: [PATCH 07/17] fix: table width and sizing --- app/admin/page.tsx | 6 +++--- components/AdminDashboardTable.tsx | 14 +++++++------- components/StatCard.tsx | 2 +- components/StatusChip.tsx | 5 ++++- components/Table.tsx | 8 +++++--- components/Tabs.tsx | 4 ++-- 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/app/admin/page.tsx b/app/admin/page.tsx index 5959af7..f0c9774 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -33,13 +33,13 @@ export default function AdminDashboard() { currentTab={currentTab} setCurrentTab={setCurrentTab} additionalClasses={{ - wrapper: 'ml:7 mt-10 md:mt-16 lg:mt-20 relative left-0', + wrapper: 'mt-10 md:mt-16 lg:mt-20', }} /> - {/* */} + />
); diff --git a/components/AdminDashboardTable.tsx b/components/AdminDashboardTable.tsx index 462f423..530cc4d 100644 --- a/components/AdminDashboardTable.tsx +++ b/components/AdminDashboardTable.tsx @@ -11,32 +11,32 @@ export default function AdminDashboardTable({ {applications.map((app) => ( - - - - - - diff --git a/components/StatCard.tsx b/components/StatCard.tsx index 4ed9594..4502cd6 100644 --- a/components/StatCard.tsx +++ b/components/StatCard.tsx @@ -10,7 +10,7 @@ export default function StatCard({ label, value }: StatCardProps) {
-

+

{value}

diff --git a/components/StatusChip.tsx b/components/StatusChip.tsx index 0aae0b3..046e95a 100644 --- a/components/StatusChip.tsx +++ b/components/StatusChip.tsx @@ -29,7 +29,10 @@ export default function StatusChip({ theme }: StatusChipProps) { style={{ backgroundColor: status.color }} >

-

+

{status.label}

diff --git a/components/Table.tsx b/components/Table.tsx index 6017fea..aea19ca 100644 --- a/components/Table.tsx +++ b/components/Table.tsx @@ -26,9 +26,11 @@ export default function Table({ additionalClasses, }: TableProps) { return ( -
+
+ {app.applicantName} + {app.type} + {formatDateWithOrdinal(app.dateReceived)} + + {app.reviewer1} + {app.reviewer2}
@@ -36,7 +38,7 @@ export default function Table({ {children} -
diff --git a/components/Tabs.tsx b/components/Tabs.tsx index 851e060..6b0d9a9 100644 --- a/components/Tabs.tsx +++ b/components/Tabs.tsx @@ -22,7 +22,7 @@ export default function Tabs({ }: TabsProps) { return (
{tabs.map((tab) => { const isActive = tab.value === currentTab.value; @@ -33,7 +33,7 @@ export default function Tabs({ onClick={() => setCurrentTab(tab)} className={` cursor-pointer font-medium text-[12px] md:text-[14px] lg:text-[16px] leading-[160%] tracking-[-0.03em] underline underline-offset-[3px] - ${isActive ? 'inline-flex items-center px-2 md:px-6 py-2 bg-[#00519F54] rounded-[30px] border-4 border-[#00519F]' : ''} + ${isActive ? 'inline-flex items-center px-2 md:px-6 py-1 md:py-2 bg-[#00519F54] rounded-[30px] border-4 border-[#00519F]' : ''} `} > {tab.label} From eb055c3a02127f3daac9e04acb014518a2f3ae62 Mon Sep 17 00:00:00 2001 From: Poorvi Date: Thu, 15 Jan 2026 22:25:25 -0500 Subject: [PATCH 08/17] fix: min w breakpoint for table --- components/Table.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/Table.tsx b/components/Table.tsx index aea19ca..a91b641 100644 --- a/components/Table.tsx +++ b/components/Table.tsx @@ -30,7 +30,7 @@ export default function Table({ className={`${inter.className} ${additionalClasses?.wrapper ?? ''} overflow-x-auto w-full`} > From a0329f62a004a1d101f9c4044ea422493aa6aac2 Mon Sep 17 00:00:00 2001 From: Poorvi Date: Sat, 24 Jan 2026 17:20:58 -0500 Subject: [PATCH 09/17] feat: implement pagination component --- app/admin/page.tsx | 1 + components/AdminDashboardTable.tsx | 76 +++++++++++------- components/IconButton.tsx | 33 ++++++++ components/Pagination.tsx | 123 +++++++++++++++++++++++++++++ components/Table.tsx | 68 ++++++++++------ types/adminDashboard.ts | 4 + 6 files changed, 254 insertions(+), 51 deletions(-) create mode 100644 components/IconButton.tsx create mode 100644 components/Pagination.tsx diff --git a/app/admin/page.tsx b/app/admin/page.tsx index f0c9774..2f07851 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -39,6 +39,7 @@ export default function AdminDashboard() { diff --git a/components/AdminDashboardTable.tsx b/components/AdminDashboardTable.tsx index 530cc4d..9498653 100644 --- a/components/AdminDashboardTable.tsx +++ b/components/AdminDashboardTable.tsx @@ -2,45 +2,67 @@ import Table from './Table'; import { formatDateWithOrdinal } from '@/lib/utils'; import StatusChip from './StatusChip'; import { AdminDashboardTablePropTypes } from '@/types/adminDashboard'; +import { useState, useMemo } from 'react'; export default function AdminDashboardTable({ columns, applications, + currentTab, }: AdminDashboardTablePropTypes) { + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 4; + + // Calculate paginated data - this will change as paginated data should come from the backend. + const paginatedApplications = useMemo(() => { + const startIndex = (currentPage - 1) * itemsPerPage; + const endIndex = startIndex + itemsPerPage; + return applications.slice(startIndex, endIndex); + }, [applications, currentPage, itemsPerPage]); + return (
itemsPerPage, + }} + onPageChange={setCurrentPage} > - {applications.map((app) => ( - - - - - - - - - - - - - - ))} + {paginatedApplications + .filter((app) => + currentTab.value !== 'all' ? app.status === currentTab.value : true, + ) + .map((app) => ( + + + + + + + + + + + + + + ))}
- {app.applicantName} - - {app.type} - - {formatDateWithOrdinal(app.dateReceived)} - - - - {app.reviewer1} - - {app.reviewer2} -
+ {app.applicantName} + + {app.type} + + {formatDateWithOrdinal(app.dateReceived)} + + + + {app.reviewer1} + + {app.reviewer2} +
); } diff --git a/components/IconButton.tsx b/components/IconButton.tsx new file mode 100644 index 0000000..f7bc28b --- /dev/null +++ b/components/IconButton.tsx @@ -0,0 +1,33 @@ +type IconButtonAdditionalClassesPropTypes = { + button: string[]; +}; + +type IconButtonComponentPropTypes = { + children: React.ReactNode; + name?: string; + value?: string; + handleClick: React.MouseEventHandler; + additionalClasses?: IconButtonAdditionalClassesPropTypes; + disabled?: boolean; +}; + +export const IconButton = ({ + children, + name, + value, + handleClick, + disabled = false, + additionalClasses, +}: Readonly) => { + return ( + + ); +}; diff --git a/components/Pagination.tsx b/components/Pagination.tsx new file mode 100644 index 0000000..27968b5 --- /dev/null +++ b/components/Pagination.tsx @@ -0,0 +1,123 @@ +import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'; +import { IconButton } from './IconButton'; +import { useState, useEffect } from 'react'; + +type PaginationPropTypes = { + activePage: number; + itemCount: number; + numberItemsPerPage?: number; + onPageChange: (page: number) => void; +}; + +type usePaginationPropTypes = { + numberItemsPerPage?: number; + activePage: number; + itemCount: number; +}; + +const usePagination = ({ + numberItemsPerPage, + itemCount, + activePage, +}: usePaginationPropTypes) => { + const [pageCount, setPageCount] = useState(1); + const [numberPerPage, setNumberPerPage] = useState(10); + + const windowSize = 5; + const maxPage = pageCount; + const shouldShift = activePage > windowSize; + + const windowEnd = shouldShift ? Math.min(activePage, maxPage) : windowSize; + const windowStart = Math.max(windowEnd - (windowSize - 1), 1); + + const setupPagination = (itemCount: number) => { + const numberOfPages = Math.ceil(itemCount / numberPerPage); + setPageCount(numberOfPages); + }; + + useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect + if (numberItemsPerPage) setNumberPerPage(numberItemsPerPage); + }, [numberItemsPerPage]); + + useEffect(() => { + if (!itemCount) return; + // eslint-disable-next-line react-hooks/set-state-in-effect + setupPagination(itemCount); + }, [itemCount, numberPerPage]); + + return { + pageCount, + windowStart, + }; +}; + +export const Pagination = ({ + activePage, + itemCount, + numberItemsPerPage, + onPageChange, +}: PaginationPropTypes) => { + const hook = usePagination({ numberItemsPerPage, itemCount, activePage }); + + if (hook.pageCount <= 1) return null; + + return ( +
+
+ onPageChange(Math.max(activePage - 1, 1))} + additionalClasses={{ + button: ['bg-[#F1F2F4] hover:bg-[#dce5fa] cursor-pointer'], + }} + > + + + +
+ {Array.from({ length: Math.min(5, hook.pageCount) }).map( + (_, index) => { + const pageNumber = hook.windowStart + index; + const isActive = activePage === pageNumber; + + return ( + onPageChange(pageNumber)} + additionalClasses={{ + button: [ + isActive + ? 'bg-[#195CFF] text-white shadow-sm' + : 'text-black bg-[#F1F2F4] hover:bg-[#dce5fa] cursor-pointer', + ], + }} + > + {pageNumber} + + ); + }, + )} +
+ + + onPageChange(Math.min(activePage + 1, hook.pageCount)) + } + additionalClasses={{ + button: ['bg-[#F1F2F4] hover:bg-[#dce5fa] cursor-pointer'], + }} + > + + +
+
+ ); +}; diff --git a/components/Table.tsx b/components/Table.tsx index a91b641..5865455 100644 --- a/components/Table.tsx +++ b/components/Table.tsx @@ -1,6 +1,7 @@ import { inter } from '@/app/fonts'; -import { ReactNode } from 'react'; +import { ReactNode, useState } from 'react'; import { ColumnType } from '@/types/adminDashboard'; +import { Pagination } from './Pagination'; type TableProps = { columnNames: ColumnType[]; @@ -18,38 +19,57 @@ type TableProps = { wrapper?: string; table?: string; }; + onPageChange?: (page: number) => void; }; export default function Table({ columnNames, children, additionalClasses, + pagination, + onPageChange, }: TableProps) { + const [activePage, setActivePage] = useState(1); + + const handlePageChange = (page: number) => { + setActivePage(page); + onPageChange?.(page); + }; + return ( -
- - - - {columnNames.map((column, index) => ( - - ))} - - +
+
+
- {column.label} -
+ + + {columnNames.map((column) => ( + + ))} + + + + {children} +
+ {column.label} +
+
-
+ {pagination?.usePagination && ( + + )}
); } diff --git a/types/adminDashboard.ts b/types/adminDashboard.ts index 6a34b33..e630dc9 100644 --- a/types/adminDashboard.ts +++ b/types/adminDashboard.ts @@ -17,4 +17,8 @@ export type ApplicationType = { export type AdminDashboardTablePropTypes = { columns: ColumnType[]; applications: ApplicationType[]; + currentTab: { + label: string; + value: string; + }; }; From 7381656a13c7432328415db3b6e71317b8460d94 Mon Sep 17 00:00:00 2001 From: Poorvi Date: Sat, 24 Jan 2026 17:25:53 -0500 Subject: [PATCH 10/17] fix: change complete to completed --- app/admin/const.ts | 2 +- components/StatusChip.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/admin/const.ts b/app/admin/const.ts index 6a27bf0..3cde6ae 100644 --- a/app/admin/const.ts +++ b/app/admin/const.ts @@ -93,7 +93,7 @@ export const ADMIN_DASHBOARD_MOCK = { applicantName: 'Carter Higgins', type: 'Organization', dateReceived: '2025-11-05', - status: 'complete', + status: 'completed', reviewer1: 'John Doe', reviewer2: 'Jane Doe', }, diff --git a/components/StatusChip.tsx b/components/StatusChip.tsx index 046e95a..d0dc9eb 100644 --- a/components/StatusChip.tsx +++ b/components/StatusChip.tsx @@ -10,7 +10,7 @@ const STATUS_CHIPS: StatusChipType[] = [ { color: '#00519F', label: 'To Review', theme: 'toReview' }, { color: '#393533', label: 'Rejected', theme: 'rejected' }, { color: '#C67D38', label: 'Payment Pending', theme: 'paymentPending' }, - { color: '#3FA104', label: 'Complete', theme: 'complete' }, + { color: '#3FA104', label: 'Complete', theme: 'completed' }, ]; type StatusChipProps = { From 5a4c7a09e40fa10eaa3bab3d2194d54f8bfbbf92 Mon Sep 17 00:00:00 2001 From: Poorvi Date: Sat, 31 Jan 2026 21:03:14 -0500 Subject: [PATCH 11/17] feat: update navbar and tabs to new style --- app/admin/const.ts | 8 ++++-- app/admin/page.tsx | 27 +++++++++----------- components/AdminNavbar.tsx | 50 ++++++-------------------------------- components/Tabs.tsx | 6 ++--- 4 files changed, 28 insertions(+), 63 deletions(-) diff --git a/app/admin/const.ts b/app/admin/const.ts index 3cde6ae..df26841 100644 --- a/app/admin/const.ts +++ b/app/admin/const.ts @@ -16,8 +16,12 @@ export const ADMIN_DASHBOARD_CONST = { value: 'paymentPending', }, { - label: 'Completed', - value: 'completed', + label: 'Active', + value: 'active', + }, + { + label: 'Expired', + value: 'expired', }, { label: 'Rejected', diff --git a/app/admin/page.tsx b/app/admin/page.tsx index 2f07851..d976d7e 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -13,34 +13,29 @@ export default function AdminDashboard() { return ( <> - -
- {ADMIN_DASHBOARD_CONST.PAGE_TITLE} -
-
-
- {ADMIN_DASHBOARD_MOCK.STAT_CARDS.map((card) => ( - - ))} -
+ +
+ {/*
+
+ {ADMIN_DASHBOARD_MOCK.STAT_CARDS.map((card) => ( + + ))} +
+ +
*/}
); diff --git a/components/AdminNavbar.tsx b/components/AdminNavbar.tsx index 9aafe34..e38b7ba 100644 --- a/components/AdminNavbar.tsx +++ b/components/AdminNavbar.tsx @@ -1,56 +1,22 @@ -import { robotoCondensed, inter } from '@/app/fonts'; +import { robotoCondensed } from '@/app/fonts'; -type AdminNavbarProps = { - adminName: string; - profilePictureUrl?: string; -}; - -export default function AdminNavbar({ - adminName, - profilePictureUrl, -}: AdminNavbarProps) { +export default function AdminNavbar() { return (

- Richmond Poverty
- Reduction Coalition + RPRC Membership Management

- -
-

- {adminName} -

- - {profilePictureUrl ? ( - - ) : ( -
- )} -
); } diff --git a/components/Tabs.tsx b/components/Tabs.tsx index 6b0d9a9..4418239 100644 --- a/components/Tabs.tsx +++ b/components/Tabs.tsx @@ -22,7 +22,7 @@ export default function Tabs({ }: TabsProps) { return (
{tabs.map((tab) => { const isActive = tab.value === currentTab.value; @@ -32,8 +32,8 @@ export default function Tabs({ key={tab.value} onClick={() => setCurrentTab(tab)} className={` - cursor-pointer font-medium text-[12px] md:text-[14px] lg:text-[16px] leading-[160%] tracking-[-0.03em] underline underline-offset-[3px] - ${isActive ? 'inline-flex items-center px-2 md:px-6 py-1 md:py-2 bg-[#00519F54] rounded-[30px] border-4 border-[#00519F]' : ''} + cursor-pointer font-semibold text-[16px] px-6.75 py-3.5 rounded-2xl border-2 border-[#BAB7B2] + ${isActive ? 'bg-[#5EB42D] border-none text-[#FFFDFA] shadow-[0px_2px_4px_-2px_#0000001A,0px_4px_6px_-1px_#0000001A]' : ''} `} > {tab.label} From 157c669aab0557b437d7f6aa78d690878e5f2663 Mon Sep 17 00:00:00 2001 From: Poorvi Date: Sun, 1 Feb 2026 13:23:16 -0500 Subject: [PATCH 12/17] feat: update statcards to new style --- app/admin/const.ts | 2 +- app/admin/page.tsx | 18 +++++++++--------- components/AdminNavbar.tsx | 10 +++++----- components/StatCard.tsx | 13 ++++++++----- components/Tabs.tsx | 8 +++++--- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/app/admin/const.ts b/app/admin/const.ts index df26841..4f86994 100644 --- a/app/admin/const.ts +++ b/app/admin/const.ts @@ -51,7 +51,7 @@ export const ADMIN_DASHBOARD_MOCK = { value: 20, }, { - label: 'New Applications Submitted', + label: 'New Applications', value: 6, }, ], diff --git a/app/admin/page.tsx b/app/admin/page.tsx index d976d7e..f4df271 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -14,21 +14,22 @@ export default function AdminDashboard() { return ( <> -
+
- {/*
-
- {ADMIN_DASHBOARD_MOCK.STAT_CARDS.map((card) => ( - - ))} -
+
+
+ {ADMIN_DASHBOARD_MOCK.STAT_CARDS.map((card) => ( + + ))} +
+ {/*
*/} -
); } diff --git a/components/AdminNavbar.tsx b/components/AdminNavbar.tsx index e38b7ba..b41f2c9 100644 --- a/components/AdminNavbar.tsx +++ b/components/AdminNavbar.tsx @@ -4,15 +4,15 @@ export default function AdminNavbar() { return (

RPRC Membership Management diff --git a/components/StatCard.tsx b/components/StatCard.tsx index 4502cd6..99c0e74 100644 --- a/components/StatCard.tsx +++ b/components/StatCard.tsx @@ -1,4 +1,4 @@ -import { inter } from '@/app/fonts'; +import { inter, robotoCondensed } from '@/app/fonts'; type StatCardProps = { label: string; @@ -8,12 +8,15 @@ type StatCardProps = { export default function StatCard({ label, value }: StatCardProps) { return (

-

- {value} +

+ + {value} + +

-

+

{label}

diff --git a/components/Tabs.tsx b/components/Tabs.tsx index 4418239..bf4f82e 100644 --- a/components/Tabs.tsx +++ b/components/Tabs.tsx @@ -22,7 +22,9 @@ export default function Tabs({ }: TabsProps) { return (
{tabs.map((tab) => { const isActive = tab.value === currentTab.value; @@ -32,8 +34,8 @@ export default function Tabs({ key={tab.value} onClick={() => setCurrentTab(tab)} className={` - cursor-pointer font-semibold text-[16px] px-6.75 py-3.5 rounded-2xl border-2 border-[#BAB7B2] - ${isActive ? 'bg-[#5EB42D] border-none text-[#FFFDFA] shadow-[0px_2px_4px_-2px_#0000001A,0px_4px_6px_-1px_#0000001A]' : ''} + cursor-pointer font-semibold text-[14px] md:text-[16px] px-4.25 md:px-6.75 py-3.25 md:py-3.5 rounded-2xl border-2 border-[#BAB7B2] text-nowrap leading-5 + ${isActive ? 'bg-[#5EB42D] border-transparent text-[#FFFDFA] shadow-[0px_2px_4px_-2px_#0000001A,0px_4px_6px_-1px_#0000001A]' : ''} `} > {tab.label} From 0d1d240f10742c295329cb24d3cd4c6f8bab0ce7 Mon Sep 17 00:00:00 2001 From: Poorvi Date: Sun, 1 Feb 2026 13:51:14 -0500 Subject: [PATCH 13/17] feat: admin dashboard search bar --- app/admin/page.tsx | 18 +++++++++++++++++- components/AdminSearchBar.tsx | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 components/AdminSearchBar.tsx diff --git a/app/admin/page.tsx b/app/admin/page.tsx index f4df271..7da889b 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -1,15 +1,28 @@ 'use client'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { inter } from '@/app/fonts'; import AdminNavbar from '@/components/AdminNavbar'; import StatCard from '@/components/StatCard'; import Tabs from '@/components/Tabs'; +import AdminSearchBar from '@/components/AdminSearchBar'; import { ADMIN_DASHBOARD_MOCK, ADMIN_DASHBOARD_CONST } from './const'; import AdminDashboardTable from '@/components/AdminDashboardTable'; export default function AdminDashboard() { const [currentTab, setCurrentTab] = useState(ADMIN_DASHBOARD_CONST.TABS[0]); + const [searchQuery, setSearchQuery] = useState(''); + const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(''); + + // Debounce the search query + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedSearchQuery(searchQuery); + console.log(debouncedSearchQuery); + }, 300); // 300ms delay + + return () => clearTimeout(timer); + }, [searchQuery]); return ( <> @@ -29,6 +42,9 @@ export default function AdminDashboard() { ))}
+
+ +
{/*
void; + placeholder?: string; +}; + +export default function AdminSearchBar({ + value, + onChange, + placeholder = 'Search by name...', +}: AdminSearchBarProps) { + return ( +
+ + onChange(e.target.value)} + className={` + ${inter.className} + w-full placeholder:text-[#A9A295] placeholder:text-[16px] focus:outline-none + `} + /> +
+ ); +} From 7deb13798bd2f020968949ddf192de25702dd1e5 Mon Sep 17 00:00:00 2001 From: Poorvi Date: Sun, 1 Feb 2026 15:01:29 -0500 Subject: [PATCH 14/17] feat: update desktop table and pagination --- app/admin/const.ts | 32 ++++++++++---- app/admin/page.tsx | 11 ++--- components/AdminDashboardTable.tsx | 51 +++++++++++++---------- components/{IconButton.tsx => Button.tsx} | 10 +++-- components/Pagination.tsx | 32 +++++--------- components/StatusChip.tsx | 22 ++++------ components/Table.tsx | 16 +++---- types/adminDashboard.ts | 6 ++- 8 files changed, 98 insertions(+), 82 deletions(-) rename components/{IconButton.tsx => Button.tsx} (61%) diff --git a/app/admin/const.ts b/app/admin/const.ts index 4f86994..9450fcd 100644 --- a/app/admin/const.ts +++ b/app/admin/const.ts @@ -29,12 +29,12 @@ export const ADMIN_DASHBOARD_CONST = { }, ], TABLE_COLUMNS: [ - { label: 'Applicant Name', value: 'applicantName', width: 'w-[260px]' }, - { label: 'Application Type', value: 'type', width: 'w-[200px]' }, - { label: 'Date Received', value: 'dateReceived', width: 'w-[200px]' }, - { label: 'Status', value: 'status', width: 'w-[280px]' }, - { label: 'Reviewer 1', value: 'reviewer1', width: 'w-[150px]' }, - { label: 'Reviewer 2', value: 'reviewer2', width: 'w-[150px]' }, + { label: 'Applicant Name', value: 'applicantName' }, + { label: 'Application Type', value: 'type' }, + { label: 'Date Received', value: 'dateReceived' }, + { label: 'Status', value: 'status' }, + { label: 'Reviewer 1', value: 'reviewer1' }, + { label: 'Reviewer 2', value: 'reviewer2' }, ], }; @@ -97,9 +97,27 @@ export const ADMIN_DASHBOARD_MOCK = { applicantName: 'Carter Higgins', type: 'Organization', dateReceived: '2025-11-05', - status: 'completed', + status: 'active', reviewer1: 'John Doe', reviewer2: 'Jane Doe', }, + { + id: 'APP06', + applicantName: 'Sarah Johnson', + type: 'Individual', + dateReceived: '2025-11-10', + status: 'active', + reviewer1: 'Jane Doe', + reviewer2: 'John Doe', + }, + { + id: 'APP07', + applicantName: 'Michael Chen', + type: 'Organization', + dateReceived: '2025-11-09', + status: 'expired', + reviewer1: 'John Doe', + reviewer2: '', + }, ] as ApplicationType[], }; diff --git a/app/admin/page.tsx b/app/admin/page.tsx index 7da889b..df04c9b 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -15,10 +15,10 @@ export default function AdminDashboard() { const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(''); // Debounce the search query + // TODO: Use the debouncedSearchQuery to filter the applications in the backend useEffect(() => { const timer = setTimeout(() => { setDebouncedSearchQuery(searchQuery); - console.log(debouncedSearchQuery); }, 300); // 300ms delay return () => clearTimeout(timer); @@ -44,15 +44,16 @@ export default function AdminDashboard() {
-
- {/*
- -
*/} +
); } diff --git a/components/AdminDashboardTable.tsx b/components/AdminDashboardTable.tsx index 9498653..8845460 100644 --- a/components/AdminDashboardTable.tsx +++ b/components/AdminDashboardTable.tsx @@ -8,16 +8,18 @@ export default function AdminDashboardTable({ columns, applications, currentTab, + pagination, }: AdminDashboardTablePropTypes) { const [currentPage, setCurrentPage] = useState(1); - const itemsPerPage = 4; - // Calculate paginated data - this will change as paginated data should come from the backend. const paginatedApplications = useMemo(() => { - const startIndex = (currentPage - 1) * itemsPerPage; - const endIndex = startIndex + itemsPerPage; + if (!pagination) { + return applications; + } + const startIndex = (currentPage - 1) * pagination.numberPerPage; + const endIndex = startIndex + pagination.numberPerPage; return applications.slice(startIndex, endIndex); - }, [applications, currentPage, itemsPerPage]); + }, [applications, currentPage, pagination]); return ( itemsPerPage, - }} + pagination={ + pagination + ? { + itemCount: pagination.itemCount, + numberPerPage: pagination.numberPerPage, + usePagination: pagination.itemCount > pagination.numberPerPage, + } + : undefined + } onPageChange={setCurrentPage} > {paginatedApplications @@ -37,29 +43,28 @@ export default function AdminDashboardTable({ currentTab.value !== 'all' ? app.status === currentTab.value : true, ) .map((app) => ( - - + + - + - - - - ))} diff --git a/components/IconButton.tsx b/components/Button.tsx similarity index 61% rename from components/IconButton.tsx rename to components/Button.tsx index f7bc28b..a31be09 100644 --- a/components/IconButton.tsx +++ b/components/Button.tsx @@ -2,30 +2,32 @@ type IconButtonAdditionalClassesPropTypes = { button: string[]; }; -type IconButtonComponentPropTypes = { +type ButtonComponentPropTypes = { children: React.ReactNode; name?: string; value?: string; handleClick: React.MouseEventHandler; additionalClasses?: IconButtonAdditionalClassesPropTypes; + isActive?: boolean; disabled?: boolean; }; -export const IconButton = ({ +export const Button = ({ children, name, value, handleClick, disabled = false, additionalClasses, -}: Readonly) => { + isActive = false, +}: Readonly) => { return ( diff --git a/components/Pagination.tsx b/components/Pagination.tsx index 27968b5..ac0d06c 100644 --- a/components/Pagination.tsx +++ b/components/Pagination.tsx @@ -1,5 +1,5 @@ import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'; -import { IconButton } from './IconButton'; +import { Button } from './Button'; import { useState, useEffect } from 'react'; type PaginationPropTypes = { @@ -65,17 +65,14 @@ export const Pagination = ({ return (
- onPageChange(Math.max(activePage - 1, 1))} - additionalClasses={{ - button: ['bg-[#F1F2F4] hover:bg-[#dce5fa] cursor-pointer'], - }} > - - + Previous +
{Array.from({ length: Math.min(5, hook.pageCount) }).map( @@ -84,39 +81,30 @@ export const Pagination = ({ const isActive = activePage === pageNumber; return ( - onPageChange(pageNumber)} - additionalClasses={{ - button: [ - isActive - ? 'bg-[#195CFF] text-white shadow-sm' - : 'text-black bg-[#F1F2F4] hover:bg-[#dce5fa] cursor-pointer', - ], - }} + isActive={isActive} > {pageNumber} - + ); }, )}
- onPageChange(Math.min(activePage + 1, hook.pageCount)) } - additionalClasses={{ - button: ['bg-[#F1F2F4] hover:bg-[#dce5fa] cursor-pointer'], - }} > - - + Next +
); diff --git a/components/StatusChip.tsx b/components/StatusChip.tsx index d0dc9eb..c0face2 100644 --- a/components/StatusChip.tsx +++ b/components/StatusChip.tsx @@ -10,11 +10,12 @@ const STATUS_CHIPS: StatusChipType[] = [ { color: '#00519F', label: 'To Review', theme: 'toReview' }, { color: '#393533', label: 'Rejected', theme: 'rejected' }, { color: '#C67D38', label: 'Payment Pending', theme: 'paymentPending' }, - { color: '#3FA104', label: 'Complete', theme: 'completed' }, + { color: '#5EB42D', label: 'Active', theme: 'active' }, + { color: '#BE282B', label: 'Expired', theme: 'expired' }, ]; type StatusChipProps = { - theme: 'toReview' | 'rejected' | 'paymentPending' | 'complete'; + theme: 'toReview' | 'rejected' | 'paymentPending' | 'active' | 'expired'; }; export default function StatusChip({ theme }: StatusChipProps) { @@ -23,18 +24,13 @@ export default function StatusChip({ theme }: StatusChipProps) { if (!status) return null; return ( -
-
+
+
-

- {status.label} -

+

{status.label}

); } diff --git a/components/Table.tsx b/components/Table.tsx index 5865455..bc1786f 100644 --- a/components/Table.tsx +++ b/components/Table.tsx @@ -38,18 +38,20 @@ export default function Table({ return (
-
+
- {app.applicantName} -
{app.applicantName} - {app.type} - {app.type} + {formatDateWithOrdinal(app.dateReceived)} + - {app.reviewer1} + + {app.reviewer1 === '' ? '-' : app.reviewer1} - {app.reviewer2} + + {app.reviewer2 === '' ? '-' : app.reviewer2}
- - {columnNames.map((column) => ( + + {columnNames.map((column, idx) => (
{column.label} diff --git a/types/adminDashboard.ts b/types/adminDashboard.ts index e630dc9..96760e2 100644 --- a/types/adminDashboard.ts +++ b/types/adminDashboard.ts @@ -9,7 +9,7 @@ export type ApplicationType = { applicantName: string; type: 'Individual' | 'Organization'; dateReceived: string | Date; - status: 'toReview' | 'rejected' | 'paymentPending' | 'complete'; + status: 'toReview' | 'rejected' | 'paymentPending' | 'active' | 'expired'; reviewer1: string; reviewer2: string; }; @@ -21,4 +21,8 @@ export type AdminDashboardTablePropTypes = { label: string; value: string; }; + pagination?: { + itemCount: number; + numberPerPage: number; + }; }; From 74faed48061af10c8b4f614ce8b09df28e8e6065 Mon Sep 17 00:00:00 2001 From: Poorvi Date: Sun, 1 Feb 2026 15:12:14 -0500 Subject: [PATCH 15/17] fix: filtering and pagination order --- components/AdminDashboardTable.tsx | 65 ++++++++++++++++-------------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/components/AdminDashboardTable.tsx b/components/AdminDashboardTable.tsx index 8845460..3f923c4 100644 --- a/components/AdminDashboardTable.tsx +++ b/components/AdminDashboardTable.tsx @@ -12,14 +12,22 @@ export default function AdminDashboardTable({ }: AdminDashboardTablePropTypes) { const [currentPage, setCurrentPage] = useState(1); + // TODO: This should be handled in the backend + const filteredApplications = useMemo(() => { + return applications.filter((app) => + currentTab.value !== 'all' ? app.status === currentTab.value : true, + ); + }, [applications, currentTab]); + + // TODO: This should be handled in the backend const paginatedApplications = useMemo(() => { if (!pagination) { - return applications; + return filteredApplications; } const startIndex = (currentPage - 1) * pagination.numberPerPage; const endIndex = startIndex + pagination.numberPerPage; - return applications.slice(startIndex, endIndex); - }, [applications, currentPage, pagination]); + return filteredApplications.slice(startIndex, endIndex); + }, [filteredApplications, currentPage, pagination]); return ( pagination.numberPerPage, + usePagination: + filteredApplications.length > pagination.numberPerPage, } : undefined } onPageChange={setCurrentPage} > - {paginatedApplications - .filter((app) => - currentTab.value !== 'all' ? app.status === currentTab.value : true, - ) - .map((app) => ( - - + {paginatedApplications.map((app) => ( + + - + - + - + - + - - - ))} + + + ))}
{app.applicantName}
{app.applicantName}{app.type}{app.type} - {formatDateWithOrdinal(app.dateReceived)} - + {formatDateWithOrdinal(app.dateReceived)} + - - + + - {app.reviewer1 === '' ? '-' : app.reviewer1} - + {app.reviewer1 === '' ? '-' : app.reviewer1} + - {app.reviewer2 === '' ? '-' : app.reviewer2} -
+ {app.reviewer2 === '' ? '-' : app.reviewer2} +
); } From 9d1c85df10b224838e1f97a5956b65bc7600668f Mon Sep 17 00:00:00 2001 From: Poorvi Date: Sun, 1 Feb 2026 15:50:47 -0500 Subject: [PATCH 16/17] feat: applications on mobile view --- app/admin/page.tsx | 31 ++++++--- components/AdminDashboardMobileTable.tsx | 89 ++++++++++++++++++++++++ components/Pagination.tsx | 2 +- types/adminDashboard.ts | 12 ++++ 4 files changed, 124 insertions(+), 10 deletions(-) create mode 100644 components/AdminDashboardMobileTable.tsx diff --git a/app/admin/page.tsx b/app/admin/page.tsx index df04c9b..70acaf3 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -8,6 +8,7 @@ import Tabs from '@/components/Tabs'; import AdminSearchBar from '@/components/AdminSearchBar'; import { ADMIN_DASHBOARD_MOCK, ADMIN_DASHBOARD_CONST } from './const'; import AdminDashboardTable from '@/components/AdminDashboardTable'; +import AdminDashboardMobileTable from '@/components/AdminDashboardMobileTable'; export default function AdminDashboard() { const [currentTab, setCurrentTab] = useState(ADMIN_DASHBOARD_CONST.TABS[0]); @@ -44,15 +45,27 @@ export default function AdminDashboard() {
- +
+ +
+
+ +
); diff --git a/components/AdminDashboardMobileTable.tsx b/components/AdminDashboardMobileTable.tsx new file mode 100644 index 0000000..5821ff2 --- /dev/null +++ b/components/AdminDashboardMobileTable.tsx @@ -0,0 +1,89 @@ +import Table from './Table'; +import { formatDateWithOrdinal } from '@/lib/utils'; +import StatusChip from './StatusChip'; +import { AdminDashboardMobileTablePropTypes } from '@/types/adminDashboard'; +import { useState, useMemo } from 'react'; +import { Pagination } from './Pagination'; + +export default function AdminDashboardMobileTable({ + applications, + currentTab, + pagination, +}: AdminDashboardMobileTablePropTypes) { + const [currentPage, setCurrentPage] = useState(1); + + const handlePageChange = (page: number) => { + setCurrentPage(page); + }; + + // TODO: This should be handled in the backend + const filteredApplications = useMemo(() => { + return applications.filter((app) => + currentTab.value !== 'all' ? app.status === currentTab.value : true, + ); + }, [applications, currentTab]); + + // TODO: This should be handled in the backend + const paginatedApplications = useMemo(() => { + if (!pagination) { + return filteredApplications; + } + const startIndex = (currentPage - 1) * pagination.numberPerPage; + const endIndex = startIndex + pagination.numberPerPage; + return filteredApplications.slice(startIndex, endIndex); + }, [filteredApplications, currentPage, pagination]); + + return ( + <> +
+ {paginatedApplications.map((app) => ( +
+
+
{app.applicantName}
+
+ +
+
+
+
+

Type

+

{app.type}

+
+
+

Date Received

+

+ {formatDateWithOrdinal(app.dateReceived)} +

+
+
+
+
+

Reviewer 1

+

+ {app.reviewer1 === '' ? '-' : app.reviewer1} +

+
+
+

Reviewer 2

+

+ {app.reviewer2 === '' ? '-' : app.reviewer2} +

+
+
+
+ ))} +
+ {pagination && ( + + )} + + ); +} diff --git a/components/Pagination.tsx b/components/Pagination.tsx index ac0d06c..46134e9 100644 --- a/components/Pagination.tsx +++ b/components/Pagination.tsx @@ -63,7 +63,7 @@ export const Pagination = ({ if (hook.pageCount <= 1) return null; return ( -
+
-
+
{children} diff --git a/components/Tabs.tsx b/components/Tabs.tsx index bf4f82e..19450cd 100644 --- a/components/Tabs.tsx +++ b/components/Tabs.tsx @@ -34,7 +34,7 @@ export default function Tabs({ key={tab.value} onClick={() => setCurrentTab(tab)} className={` - cursor-pointer font-semibold text-[14px] md:text-[16px] px-4.25 md:px-6.75 py-3.25 md:py-3.5 rounded-2xl border-2 border-[#BAB7B2] text-nowrap leading-5 + cursor-pointer font-semibold text-[14px] md:text-[16px] px-4.25 md:px-6.75 py-3.25 md:py-3.5 rounded-2xl border-2 border-[#BAB7B2] text-nowrap leading-5 hover:bg-[#2B8100] hover:text-[#FFFDFA] hover:border-transparent transition-all ${isActive ? 'bg-[#5EB42D] border-transparent text-[#FFFDFA] shadow-[0px_2px_4px_-2px_#0000001A,0px_4px_6px_-1px_#0000001A]' : ''} `} >