diff --git a/app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx b/app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx
index b5a17ef1..e475a6a9 100644
--- a/app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx
+++ b/app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx
@@ -43,8 +43,8 @@ interface FilterState {
const mapFiltersToParams = (filters: FilterState, searchOverride?: string) => ({
search: searchOverride !== undefined ? searchOverride : filters.search,
- status: filters.status === 'all' ? undefined : filters.status,
- type: filters.type === 'all' ? undefined : filters.type,
+ status: filters.status === 'all' ? undefined : filters.status.toUpperCase(),
+ type: filters.type === 'all' ? undefined : filters.type.toUpperCase(),
});
const ParticipantsPage: React.FC = () => {
@@ -228,9 +228,45 @@ const ParticipantsPage: React.FC = () => {
}
};
+ // Frontend-side filtering as per requirement
+ const filteredParticipants = useMemo(() => {
+ return participants.filter(participant => {
+ // Search: filter by name or username
+ const search = filters.search.toLowerCase();
+ const matchesSearch = search
+ ? (participant.user?.profile?.name || '')
+ .toLowerCase()
+ .includes(search) ||
+ (participant.user?.profile?.username || '')
+ .toLowerCase()
+ .includes(search)
+ : true;
+
+ // Status: filter by participant.submission.status
+ // Filter values are 'submitted', 'not_submitted', etc.
+ // ParticipantSubmission.status values are 'submitted', 'shortlisted', etc.
+ const matchesStatus =
+ filters.status === 'all'
+ ? true
+ : filters.status === 'not_submitted'
+ ? !participant.submission
+ : participant.submission?.status?.toLowerCase() ===
+ filters.status.toLowerCase();
+
+ // Type: filter by participant.participationType
+ const matchesType =
+ filters.type === 'all'
+ ? true
+ : participant.participationType?.toLowerCase() ===
+ filters.type.toLowerCase();
+
+ return matchesSearch && matchesStatus && matchesType;
+ });
+ }, [participants, filters.search, filters.status, filters.type]);
+
// Mock table instance for DataTablePagination
const table = useReactTable({
- data: participants,
+ data: filteredParticipants,
columns: [], // Not used for rendering here
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
@@ -316,7 +352,7 @@ const ParticipantsPage: React.FC = () => {
{view === 'table' ? (
{
/>
) : (
(null);
@@ -34,7 +41,6 @@ export default function SubmissionsPage() {
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);
+ }
+ }
+ },
+ });
+
if (error) {
return (
@@ -100,7 +152,7 @@ export default function SubmissionsPage() {
{/* Main Content */}
- {loading && submissions.length === 0 ? (
+ {loading && allSubmissions.length === 0 ? (
@@ -108,19 +160,25 @@ export default function SubmissionsPage() {
) : (
-
+
)}
diff --git a/components/hackathons/submissions/SubmissionDetailModal.tsx b/components/hackathons/submissions/SubmissionDetailModal.tsx
index 3cb2bf64..99413574 100644
--- a/components/hackathons/submissions/SubmissionDetailModal.tsx
+++ b/components/hackathons/submissions/SubmissionDetailModal.tsx
@@ -166,16 +166,16 @@ export function SubmissionDetailModal({
- {submission.status === 'shortlisted'
+ {submission.status === 'SHORTLISTED'
? 'Shortlisted'
- : submission.status === 'disqualified'
+ : submission.status === 'DISQUALIFIED'
? 'Disqualified'
: 'Submitted'}
@@ -295,7 +295,7 @@ export function SubmissionDetailModal({
{/* Disqualification Reason */}
- {submission.status === 'disqualified' &&
+ {submission.status === 'DISQUALIFIED' &&
submission.disqualificationReason && (
diff --git a/components/organization/cards/Participant.tsx b/components/organization/cards/Participant.tsx
index 8d527083..c62bf112 100644
--- a/components/organization/cards/Participant.tsx
+++ b/components/organization/cards/Participant.tsx
@@ -45,7 +45,7 @@ const Participant = ({
// Check if submission is shortlisted
const isShortlisted = useMemo(() => {
- return participant.submission?.status === 'shortlisted';
+ return participant.submission?.status === 'SHORTLISTED';
}, [participant.submission?.status]);
// Fetch criteria when opening judge modal
diff --git a/components/organization/hackathons/ParticipantsGrid.tsx b/components/organization/hackathons/ParticipantsGrid.tsx
index 42d3a4ed..72281d88 100644
--- a/components/organization/hackathons/ParticipantsGrid.tsx
+++ b/components/organization/hackathons/ParticipantsGrid.tsx
@@ -67,8 +67,8 @@ export const ParticipantsGrid: React.FC = ({
const user = participant.user;
const submission = participant.submission;
const hasSubmission = !!submission;
- const isShortlisted = submission?.status === 'shortlisted';
- const isDisqualified = submission?.status === 'disqualified';
+ const isShortlisted = submission?.status === 'SHORTLISTED';
+ const isDisqualified = submission?.status === 'DISQUALIFIED';
const isTeam = participant.participationType === 'team';
const votesCount = Array.isArray(submission?.votes)
diff --git a/components/organization/hackathons/ParticipantsTable.tsx b/components/organization/hackathons/ParticipantsTable.tsx
index 1ab5a3ee..c1e141b8 100644
--- a/components/organization/hackathons/ParticipantsTable.tsx
+++ b/components/organization/hackathons/ParticipantsTable.tsx
@@ -110,22 +110,25 @@ export function ParticipantsTable({
);
}
- const status = submission.status;
+ const status = submission.status as
+ | 'SHORTLISTED'
+ | 'DISQUALIFIED'
+ | 'SUBMITTED';
return (
- {status === 'shortlisted'
+ {status === 'SHORTLISTED'
? 'Shortlisted'
- : status === 'disqualified'
+ : status === 'DISQUALIFIED'
? 'Disqualified'
: 'Submitted'}
@@ -150,7 +153,7 @@ export function ParticipantsTable({
const participant = row.original;
const hasSubmission = !!participant.submission;
const isShortlisted =
- participant.submission?.status === 'shortlisted';
+ participant.submission?.status === 'SHORTLISTED';
return (
diff --git a/components/organization/hackathons/submissions/SubmissionsManagement.tsx b/components/organization/hackathons/submissions/SubmissionsManagement.tsx
index f75edeab..3a55e8a2 100644
--- a/components/organization/hackathons/submissions/SubmissionsManagement.tsx
+++ b/components/organization/hackathons/submissions/SubmissionsManagement.tsx
@@ -32,11 +32,7 @@ import { useUpdateRank } from '@/hooks/hackathon/use-update-rank';
/* Types */
/* -------------------------------------------------------------------------- */
-type SubmissionStatus =
- | 'SUBMITTED'
- | 'SHORTLISTED'
- | 'DISQUALIFIED'
- | 'WITHDRAWN';
+type SubmissionStatus = 'SUBMITTED' | 'SHORTLISTED' | 'DISQUALIFIED';
type SubmissionType = 'INDIVIDUAL' | 'TEAM';
@@ -58,6 +54,7 @@ interface SubmissionsManagementProps {
loading: boolean;
onFilterChange: (filters: SubmissionFilters) => void;
onPageChange: (page: number) => void;
+ onPageSizeChange?: (size: number) => void;
onRefresh: () => void;
organizationId?: string;
hackathonId?: string;
@@ -83,7 +80,6 @@ export function SubmissionsManagement({
hackathon,
}: SubmissionsManagementProps) {
const [viewMode, setViewMode] = useState<'grid' | 'table'>('grid');
- const [searchTerm, setSearchTerm] = useState(filters.search ?? '');
const [selectedIds, setSelectedIds] = useState([]);
const [showBulkDisqualifyDialog, setShowBulkDisqualifyDialog] =
useState(false);
@@ -143,9 +139,8 @@ export function SubmissionsManagement({
onRefresh();
};
- const handleSearchSubmit = (e: FormEvent) => {
- e.preventDefault();
- onFilterChange({ ...filters, search: searchTerm });
+ const handleSearchTermChange = (value: string) => {
+ onFilterChange({ ...filters, search: value });
};
const handleStatusChange = (value: string) => {
@@ -214,18 +209,16 @@ export function SubmissionsManagement({
) : (
-
+
+
+ handleSearchTermChange(e.target.value)}
+ className='focus:border-primary focus:ring-primary border-gray-700/50 bg-gray-900/50 pl-10 text-white placeholder:text-gray-500'
+ />
+
)}
@@ -244,7 +237,6 @@ export function SubmissionsManagement({
Submitted
Shortlisted
Disqualified
- Withdrawn
@@ -338,32 +330,7 @@ export function SubmissionsManagement({
isSubmitting={isBulkLoading}
/>
- {/* Pagination */}
- {pagination.totalPages > 1 && (
-
-
-
- Page {pagination.page} of {pagination.totalPages}
-
-
-
- )}
+ {/* Pagination removed - controlled externally by page.tsx */}
);
}
diff --git a/hooks/hackathon/use-organizer-submissions.ts b/hooks/hackathon/use-organizer-submissions.ts
index 504b6eb0..f0b402fd 100644
--- a/hooks/hackathon/use-organizer-submissions.ts
+++ b/hooks/hackathon/use-organizer-submissions.ts
@@ -1,9 +1,10 @@
-import { useState, useCallback } from 'react';
+import { useState, useCallback, useEffect } from 'react';
import { getHackathonSubmissions } from '@/lib/api/hackathons';
import type { ParticipantSubmission } from '@/lib/api/hackathons';
+import { useDebounce } from '@/hooks/use-debounce';
export type OrganizerSubmissionFilters = {
- status?: 'SUBMITTED' | 'SHORTLISTED' | 'DISQUALIFIED' | 'WITHDRAWN';
+ status?: 'SUBMITTED' | 'SHORTLISTED' | 'DISQUALIFIED';
type?: 'INDIVIDUAL' | 'TEAM';
search?: string;
};
@@ -33,6 +34,7 @@ export interface UseOrganizerSubmissionsReturn {
) => Promise;
goToPage: (page: number) => void;
refresh: () => void;
+ updateLimit: (limit: number) => void;
}
export function useOrganizerSubmissions(
@@ -47,6 +49,8 @@ export function useOrganizerSubmissions(
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
+ const debouncedSearch = useDebounce(filters.search, 500);
+
const fetchSubmissions = useCallback(
async (page = 1, filterOverrides?: OrganizerSubmissionFilters) => {
if (!hackathonId) return;
@@ -55,24 +59,32 @@ export function useOrganizerSubmissions(
setError(null);
try {
- const appliedFilters = filterOverrides ?? filters;
+ const appliedFilters = filterOverrides ?? {
+ ...filters,
+ search: debouncedSearch,
+ };
const res = await getHackathonSubmissions(
hackathonId,
page,
- initialLimit,
+ pagination.limit || initialLimit,
appliedFilters
);
const list = res.data?.submissions ?? [];
- const pag = res.data?.pagination ?? {
- page: 1,
- limit: initialLimit,
- total: 0,
- totalPages: 0,
- };
+ const rawPag = res.data?.pagination;
+
+ const currentPage = rawPag?.page || page;
+ const limit = rawPag?.limit || pagination.limit || initialLimit;
+ const total = rawPag?.total || 0;
+ const totalPages = rawPag?.totalPages || Math.ceil(total / limit) || 1;
setSubmissions(list);
- setPagination(pag);
+ setPagination({
+ page: currentPage,
+ limit,
+ total,
+ totalPages,
+ });
} catch (err) {
setError(
err instanceof Error ? err.message : 'Failed to fetch submissions'
@@ -83,16 +95,30 @@ export function useOrganizerSubmissions(
setLoading(false);
}
},
- [hackathonId, initialLimit, filters]
+ [
+ hackathonId,
+ initialLimit,
+ debouncedSearch,
+ pagination.limit,
+ filters.status,
+ filters.type,
+ ]
);
- const updateFilters = useCallback(
- (next: OrganizerSubmissionFilters) => {
- setFilters(next);
- fetchSubmissions(1, next);
- },
- [fetchSubmissions]
- );
+ // Sync with backend on filter/pagination changes
+ useEffect(() => {
+ fetchSubmissions(1);
+ }, [
+ fetchSubmissions,
+ debouncedSearch,
+ filters.status,
+ filters.type,
+ pagination.limit,
+ ]);
+
+ const updateFilters = useCallback((next: OrganizerSubmissionFilters) => {
+ setFilters(next);
+ }, []);
const goToPage = useCallback(
(page: number) => {
@@ -105,6 +131,10 @@ export function useOrganizerSubmissions(
fetchSubmissions(pagination.page);
}, [fetchSubmissions, pagination.page]);
+ const updateLimit = useCallback((nextLimit: number) => {
+ setPagination(prev => ({ ...prev, limit: nextLimit }));
+ }, []);
+
return {
submissions,
pagination,
@@ -115,5 +145,6 @@ export function useOrganizerSubmissions(
fetchSubmissions,
goToPage,
refresh,
+ updateLimit,
};
}
diff --git a/hooks/hackathon/use-register-hackathon.ts b/hooks/hackathon/use-register-hackathon.ts
index f15a93b9..4e7cb921 100644
--- a/hooks/hackathon/use-register-hackathon.ts
+++ b/hooks/hackathon/use-register-hackathon.ts
@@ -122,6 +122,6 @@ export function useRegisterHackathon({
hasCheckedInitially,
// Expose setters for immediate updates
setParticipant,
- hasSubmitted: participant?.submission?.status === 'submitted',
+ hasSubmitted: participant?.submission?.status === 'SUBMITTED',
};
}
diff --git a/hooks/use-hackathons.ts b/hooks/use-hackathons.ts
index 4b56eff7..f6a79a58 100644
--- a/hooks/use-hackathons.ts
+++ b/hooks/use-hackathons.ts
@@ -31,8 +31,8 @@ export interface UseHackathonsOptions {
search?: string;
};
participantFilters?: {
- status?: 'submitted' | 'not_submitted' | 'shortlisted' | 'disqualified';
- type?: 'individual' | 'team';
+ status?: string;
+ type?: string;
search?: string;
};
}
@@ -579,16 +579,22 @@ export function useHackathons(
const pagination = (response.data?.pagination ||
response.meta?.pagination) as any;
+ const responsePage = pagination?.page || 1;
+ const totalItems = pagination?.total || 0;
+ const itemsPerPage = pagination?.limit || pageSize;
+ const totalPages =
+ pagination?.totalPages || Math.ceil(totalItems / itemsPerPage) || 1;
+
setParticipants(response.data?.participants || []);
setParticipantsPagination({
- currentPage: pagination?.page || 1,
- totalPages: pagination?.totalPages || 1,
- totalItems: pagination?.total || 0,
- itemsPerPage: pagination?.limit || pageSize,
- hasNext: !!pagination?.hasNext,
- hasPrev: !!pagination?.hasPrev,
+ currentPage: responsePage,
+ totalPages,
+ totalItems,
+ itemsPerPage,
+ hasNext: !!pagination?.hasNext || responsePage < totalPages,
+ hasPrev: !!pagination?.hasPrev || responsePage > 1,
});
- participantsPageRef.current = pagination?.page || 1;
+ participantsPageRef.current = responsePage;
} catch (error) {
const errorMessage =
error instanceof Error
diff --git a/hooks/use-submission-actions.ts b/hooks/use-submission-actions.ts
index 64216884..fd43b9d2 100644
--- a/hooks/use-submission-actions.ts
+++ b/hooks/use-submission-actions.ts
@@ -35,7 +35,7 @@ export const useSubmissionActions = ({
if (response.success && response.data) {
const newStatus = response.data.submission?.status;
- const isShortlisted = newStatus === 'shortlisted';
+ const isShortlisted = newStatus === 'SHORTLISTED';
toast.success(
isShortlisted
@@ -84,7 +84,7 @@ export const useSubmissionActions = ({
if (response.success && response.data) {
const newStatus = response.data.submission?.status;
- const isDisqualified = newStatus === 'disqualified';
+ const isDisqualified = newStatus === 'DISQUALIFIED';
toast.success(
isDisqualified
diff --git a/lib/api/hackathons.ts b/lib/api/hackathons.ts
index dc223a57..a105755c 100644
--- a/lib/api/hackathons.ts
+++ b/lib/api/hackathons.ts
@@ -692,7 +692,14 @@ export interface ParticipantSubmission {
comments?: number | ParticipantComment[];
submissionDate?: string;
submittedAt?: string;
- status: 'submitted' | 'shortlisted' | 'disqualified' | string;
+ status: SubmissionStatus;
+ participationType?: 'INDIVIDUAL' | 'TEAM' | 'TEAM_OR_INDIVIDUAL';
+ participant?: {
+ id: string;
+ name: string;
+ username: string;
+ image?: string;
+ };
disqualificationReason?: string | null;
reviewedBy?: {
id: string;