-
Notifications
You must be signed in to change notification settings - Fork 88
Pr 486 #488
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pr 486 #488
Changes from all commits
9dc0742
f22f059
0730da5
00b1dcf
9a2e13f
ebc1875
d8880ab
b6738bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,24 +1,30 @@ | ||
| 'use client'; | ||
|
|
||
| import { useParams } from 'next/navigation'; | ||
| import { useEffect, useState } from 'react'; | ||
| import { useEffect, useState, useMemo } from 'react'; | ||
| import { Loader2, AlertCircle } from 'lucide-react'; | ||
| import { useOrganizerSubmissions } from '@/hooks/hackathon/use-organizer-submissions'; | ||
| import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; | ||
| import { AuthGuard } from '@/components/auth'; | ||
| import Loading from '@/components/Loading'; | ||
| import { SubmissionsManagement } from '@/components/organization/hackathons/submissions/SubmissionsManagement'; | ||
| import { authClient } from '@/lib/auth-client'; | ||
| import { getHackathon, type Hackathon } from '@/lib/api/hackathons'; | ||
| import { | ||
| getHackathon, | ||
| type Hackathon, | ||
| type ParticipantSubmission, | ||
| } from '@/lib/api/hackathons'; | ||
| import { reportError } from '@/lib/error-reporting'; | ||
| import { useReactTable, getCoreRowModel } from '@tanstack/react-table'; | ||
| import { DataTablePagination } from '@/components/ui/data-table-pagination'; | ||
|
|
||
| export default function SubmissionsPage() { | ||
| const params = useParams(); | ||
| const hackathonId = params.hackathonId as string; | ||
| const organizationId = params.id as string; | ||
|
|
||
| const { | ||
| submissions, | ||
| submissions: allSubmissions, | ||
| pagination, | ||
| filters, | ||
| loading, | ||
|
|
@@ -27,14 +33,14 @@ export default function SubmissionsPage() { | |
| updateFilters, | ||
| goToPage, | ||
| refresh, | ||
| updateLimit, | ||
| } = useOrganizerSubmissions(hackathonId); | ||
|
|
||
| const [currentUserId, setCurrentUserId] = useState<string | null>(null); | ||
| const [hackathon, setHackathon] = useState<Hackathon | null>(null); | ||
|
|
||
| useEffect(() => { | ||
| if (hackathonId) { | ||
| fetchSubmissions(); | ||
| const fetchHackathonDetails = async () => { | ||
| try { | ||
| const res = await getHackathon(hackathonId); | ||
|
|
@@ -50,7 +56,7 @@ export default function SubmissionsPage() { | |
| }; | ||
| fetchHackathonDetails(); | ||
| } | ||
| }, [hackathonId, fetchSubmissions]); | ||
| }, [hackathonId]); | ||
|
|
||
| useEffect(() => { | ||
| const fetchSession = async () => { | ||
|
|
@@ -66,6 +72,52 @@ export default function SubmissionsPage() { | |
| fetchSession(); | ||
| }, []); | ||
|
|
||
| // Frontend-side filtering | ||
| const filteredSubmissions = useMemo(() => { | ||
| return allSubmissions.filter(sub => { | ||
| const search = filters.search?.toLowerCase() || ''; | ||
| const matchesSearch = search | ||
| ? (sub.projectName || '').toLowerCase().includes(search) || | ||
| (sub.participant?.name || '').toLowerCase().includes(search) || | ||
| (sub.participant?.username || '').toLowerCase().includes(search) | ||
| : true; | ||
|
|
||
| const matchesStatus = !filters.status || sub.status === filters.status; | ||
|
|
||
| const matchesType = | ||
| !filters.type || sub.participationType === filters.type; | ||
|
|
||
| return matchesSearch && matchesStatus && matchesType; | ||
| }); | ||
| }, [allSubmissions, filters.search, filters.status, filters.type]); | ||
|
|
||
| const table = useReactTable({ | ||
| data: filteredSubmissions, | ||
| columns: [], | ||
| getCoreRowModel: getCoreRowModel(), | ||
| manualPagination: true, | ||
| pageCount: pagination.totalPages, | ||
| state: { | ||
| pagination: { | ||
| pageIndex: pagination.page - 1, | ||
| pageSize: pagination.limit, | ||
| }, | ||
| }, | ||
| onPaginationChange: updater => { | ||
| if (typeof updater === 'function') { | ||
| const newState = updater({ | ||
| pageIndex: pagination.page - 1, | ||
| pageSize: pagination.limit, | ||
| }); | ||
| if (newState.pageSize !== pagination.limit) { | ||
| updateLimit(newState.pageSize); | ||
| } else { | ||
| goToPage(newState.pageIndex + 1); | ||
| } | ||
| } | ||
| }, | ||
| }); | ||
|
Comment on lines
+75
to
+119
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Frontend filtering only operates on the current page, not the full dataset. The current implementation has a fundamental mismatch between filtering and pagination:
This creates confusing UX where users search for a term, see "0 results" on the current page, but pagination still shows 50 pages available. Navigating to another page fetches fresh backend data, making the filter appear to reset. The backend already supports server-side filtering via Consider removing the frontend 🔧 Suggested approach- // Frontend-side filtering
- const filteredSubmissions = useMemo(() => {
- return allSubmissions.filter(sub => {
- const search = filters.search?.toLowerCase() || '';
- const matchesSearch = search
- ? (sub.projectName || '').toLowerCase().includes(search) ||
- (sub.participant?.name || '').toLowerCase().includes(search) ||
- (sub.participant?.username || '').toLowerCase().includes(search)
- : true;
-
- const matchesStatus = !filters.status || sub.status === filters.status;
-
- const matchesType =
- !filters.type || sub.participationType === filters.type;
-
- return matchesSearch && matchesStatus && matchesType;
- });
- }, [allSubmissions, filters.search, filters.status, filters.type]);
// ... in render:
<SubmissionsManagement
- submissions={filteredSubmissions}
+ submissions={allSubmissions}
// ... rest of props
/>This ensures filtering and pagination are both handled server-side, providing accurate page counts and complete dataset filtering. 🤖 Prompt for AI Agents
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea but the backend doesnt have filters implemented. We only implemented pagination and page size There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There is still a smaller residual issue worth noting: when filters are active, A minimal fix would be to compute a local page count based on the filtered results for display purposes: const filteredPageCount = Math.max(1, Math.ceil(filteredSubmissions.length / pagination.limit));
const table = useReactTable({
data: filteredSubmissions,
columns: [],
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
pageCount: filters.search || filters.status || filters.type
? filteredPageCount // local estimate when filters are active
: pagination.totalPages, // server total when no filters
// ...rest unchanged
});This keeps the server-driven pagination when there are no active filters, and falls back to a locally-derived page count when filters narrow the results. It's not perfect (since filtering only covers the loaded page), but it avoids the confusing "0 results — Page 1 of 50" display. That said, this is a known trade-off with client-side filtering over paginated data. If the datasets stay small enough to load all at once in the future, a full client-side approach (no server pagination + useMemo filter) would eliminate the ambiguity entirely. ✏️ Learnings added
|
||
|
|
||
| if (error) { | ||
| return ( | ||
| <div className='flex min-h-screen items-center justify-center bg-black p-6'> | ||
|
|
@@ -100,27 +152,33 @@ export default function SubmissionsPage() { | |
|
|
||
| {/* Main Content */} | ||
| <div className='mx-auto max-w-7xl px-6 py-12 sm:px-8 lg:px-12'> | ||
| {loading && submissions.length === 0 ? ( | ||
| {loading && allSubmissions.length === 0 ? ( | ||
| <div className='flex items-center justify-center py-20'> | ||
| <div className='flex flex-col items-center gap-3'> | ||
| <Loader2 className='h-6 w-6 animate-spin text-gray-400' /> | ||
| <p className='text-sm text-gray-500'>Loading submissions...</p> | ||
| </div> | ||
| </div> | ||
| ) : ( | ||
| <SubmissionsManagement | ||
| submissions={submissions} | ||
| pagination={pagination} | ||
| filters={filters} | ||
| loading={loading} | ||
| onFilterChange={updateFilters} | ||
| onPageChange={goToPage} | ||
| onRefresh={refresh} | ||
| organizationId={organizationId} | ||
| hackathonId={hackathonId} | ||
| currentUserId={currentUserId || undefined} | ||
| hackathon={hackathon || undefined} | ||
| /> | ||
| <div className='space-y-6'> | ||
| <SubmissionsManagement | ||
| submissions={filteredSubmissions} | ||
| pagination={pagination} | ||
| filters={filters} | ||
| loading={loading} | ||
| onFilterChange={updateFilters} | ||
| onPageChange={goToPage} | ||
| onRefresh={refresh} | ||
| organizationId={organizationId} | ||
| hackathonId={hackathonId} | ||
| currentUserId={currentUserId || undefined} | ||
| hackathon={hackathon || undefined} | ||
| /> | ||
|
|
||
| <div className='flex justify-end'> | ||
| <DataTablePagination table={table} loading={loading} /> | ||
| </div> | ||
| </div> | ||
| )} | ||
| </div> | ||
| </div> | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.