import { FC, ReactElement, useState, useEffect, useMemo, useCallback, } from 'react'; import SubmissionModal from './_components/submission-modal'; import { BackofficeWrapper, DataTable, } from '@imphnen-frontend-service/ui/organisms'; import { ColumnDef } from '@tanstack/react-table'; import { Button } from '@imphnen-frontend-service/ui/atoms'; import { cn } from '@imphnen-frontend-service/utils'; import { SearchOutlined, FilterOutlined, LoadingOutlined, EyeOutlined, } from '@ant-design/icons'; import { useQuery } from '@tanstack/react-query'; import { getAdminSubmissions, TAdminSubmissionItem, } from '@imphnen-frontend-service/service'; import { useSearchParams } from 'react-router-dom'; type SubmissionType = TAdminSubmissionItem; export const HackathonSubmissionsPage: FC = (): ReactElement => { const [searchParams, setSearchParams] = useSearchParams(); const currentPage = Math.max( 1, parseInt(searchParams.get('page') || '1', 10) ); const searchQuery = searchParams.get('search') || ''; const perPage = parseInt(searchParams.get('per_page') || '10', 10); const statusFilter = searchParams.get('status') || 'all'; const [showSubmissionModal, setShowSubmissionModal] = useState(false); const [selectedSubmission, setSelectedSubmission] = useState(null); const [globalFilter, setGlobalFilter] = useState(searchQuery); // Fetch submissions from API const { data: submissionsResponse, isLoading, isFetching, } = useQuery({ queryKey: [ 'admin-submissions', currentPage, perPage, statusFilter, searchQuery, ], queryFn: () => getAdminSubmissions({ page: currentPage, per_page: perPage, status: statusFilter !== 'all' ? statusFilter : undefined, search: searchQuery || undefined, }), staleTime: 30000, // 30 seconds cache gcTime: 5 * 60 * 1000, // 5 minutes }); const totalData = submissionsResponse?.meta?.total_data || 0; const totalPages = submissionsResponse?.meta?.total_page || 1; // Handle page change const handlePageChange = useCallback( (newPage: number) => { const params = new URLSearchParams(); params.set('page', newPage.toString()); if (perPage !== 10) params.set('per_page', perPage.toString()); if (searchQuery) params.set('search', searchQuery); if (statusFilter !== 'all') params.set('status', statusFilter); setSearchParams(params); window.scrollTo({ top: 0, behavior: 'smooth' }); }, [setSearchParams, perPage, searchQuery, statusFilter] ); // Validate page number useEffect(() => { if (!isLoading && totalPages > 0 && currentPage > totalPages) { setSearchParams({ page: totalPages.toString() }); } }, [currentPage, totalPages, setSearchParams, isLoading]); // Sync globalFilter with URL useEffect(() => { setGlobalFilter(searchQuery); }, [searchQuery]); // Handle search const handleSearch = useCallback(() => { const params = new URLSearchParams(); params.set('page', '1'); if (perPage !== 10) params.set('per_page', perPage.toString()); if (globalFilter.trim()) { params.set('search', globalFilter.trim()); } if (statusFilter !== 'all') params.set('status', statusFilter); setSearchParams(params); }, [globalFilter, setSearchParams, perPage, statusFilter]); const handleSearchKeyPress = useCallback( (e: React.KeyboardEvent) => { if (e.key === 'Enter') { handleSearch(); } }, [handleSearch] ); // Handle per page change const handlePerPageChange = useCallback( (newPerPage: number) => { const params = new URLSearchParams(); params.set('page', '1'); params.set('per_page', newPerPage.toString()); if (searchQuery) params.set('search', searchQuery); if (statusFilter !== 'all') params.set('status', statusFilter); setSearchParams(params); }, [setSearchParams, searchQuery, statusFilter] ); // Handle status filter change // const handleStatusFilterChange = useCallback( // (newStatus: string) => { // const params = new URLSearchParams(); // params.set('page', '1'); // if (perPage !== 10) params.set('per_page', perPage.toString()); // if (searchQuery) params.set('search', searchQuery); // if (newStatus !== 'all') params.set('status', newStatus); // setSearchParams(params); // }, // [setSearchParams, perPage, searchQuery] // ); // Handle modal const handleShowSubmissionModal = useCallback( (submission: SubmissionType) => { setSelectedSubmission(submission); setShowSubmissionModal(true); }, [] ); const handleCloseSubmissionModal = useCallback(() => { setShowSubmissionModal(false); setSelectedSubmission(null); }, []); // Get submissions data const filteredData = useMemo(() => { return submissionsResponse?.data || []; }, [submissionsResponse]); // Memoize columns const columns: ColumnDef[] = useMemo( () => [ { accessorKey: 'project_name', header: 'Project Name', cell: ({ row }) => ( {row.original.project_name} ), enableSorting: true, }, { accessorKey: 'team_id', header: 'Team ID', cell: ({ row }) => ( {row.original.team_id} ), enableSorting: false, }, { accessorKey: 'status', header: 'Status', cell: ({ row }) => { const status = row.original.status; return ( {status.charAt(0).toUpperCase() + status.slice(1)} ); }, enableSorting: true, }, { accessorKey: 'submitted_at', header: 'Submitted', cell: ({ row }) => ( {new Date(row.original.submitted_at).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', })} ), enableSorting: true, sortingFn: 'datetime', }, { id: 'actions', header: 'Actions', meta: { cellClassName: cn('w-48') }, cell: ({ row }) => ( ), enableSorting: false, }, ], [handleShowSubmissionModal] ); return (

Project Submissions

{/* Search bar */}
setGlobalFilter(e.target.value)} onKeyPress={handleSearchKeyPress} />
{/* Per Page Dropdown */}
{/* Status Filter */} {/*
*/}
{/* Active filters */} {/* {statusFilter !== 'all' && (
Active filters: Status: {statusFilter}
)} */} {/* Loading & results */} {isLoading ? (
Loading submissions...
) : filteredData.length > 0 ? ( <>
Showing {filteredData.length} of {totalData} submissions (Page{' '} {currentPage} of {totalPages}) {isFetching && ( (Updating...) )}
) : (
No submissions found. Try adjusting your filters.
)}
{/* Submission Modal */} {selectedSubmission && ( )}
); }; export default HackathonSubmissionsPage;