From 52e2187a1b923db153de0e72c2a053c7642a4249 Mon Sep 17 00:00:00 2001 From: Kewonit <108450560+kewonit@users.noreply.github.com.> Date: Fri, 4 Jul 2025 03:08:43 +0530 Subject: [PATCH 1/2] feat: Add batch upload functionality for MHT-CET cutoffs with improved error handling and performance optimizations - Introduced new scripts for batch uploading MHT-CET cutoffs, including `batch-upload-mht-cet-cutoffs.ts` and `test-batch-api.ts`. - Updated `package.json` to include new npm scripts for batch upload and testing. - Created detailed README and documentation for batch upload functionality. - Implemented auto-cancellation fix in PocketBase SDK to handle concurrent requests. - Added Python script `map_college_info.py` for processing college information and integrating it into the cutoffs data. - Removed deprecated `upload-mht-cet-cutoffs.ts` script. - Updated `upload-mht-cet-cutoffs-v2.ts` to use the new collection name. --- app/api/mht-cet/state-cutoffs/route.ts | 68 ++- app/mht-cet/state-cutoffs/page.tsx | 587 +++++++++++++++--------- components/CategoryFlowChart.tsx | 550 ++++++++++++++++++++++ package-lock.json | 450 ++++++++++++++++++ package.json | 6 +- scripts/AUTO_CANCELLATION_FIX.md | 60 +++ scripts/BATCH_UPLOAD_README.md | 183 ++++++++ scripts/batch-upload-mht-cet-cutoffs.ts | 304 ++++++++++++ scripts/map_college_info.py | 65 +++ scripts/test-batch-api.ts | 98 ++++ scripts/upload-mht-cet-cutoffs-v2.ts | 2 +- scripts/upload-mht-cet-cutoffs.ts | 191 -------- 12 files changed, 2163 insertions(+), 401 deletions(-) create mode 100644 components/CategoryFlowChart.tsx create mode 100644 scripts/AUTO_CANCELLATION_FIX.md create mode 100644 scripts/BATCH_UPLOAD_README.md create mode 100644 scripts/batch-upload-mht-cet-cutoffs.ts create mode 100644 scripts/map_college_info.py create mode 100644 scripts/test-batch-api.ts delete mode 100644 scripts/upload-mht-cet-cutoffs.ts diff --git a/app/api/mht-cet/state-cutoffs/route.ts b/app/api/mht-cet/state-cutoffs/route.ts index 2451aa8..0763117 100644 --- a/app/api/mht-cet/state-cutoffs/route.ts +++ b/app/api/mht-cet/state-cutoffs/route.ts @@ -13,6 +13,8 @@ export async function POST(request: NextRequest) { const categories = body.categories || []; const seatAllocations = body.seatAllocations || []; const courses = body.courses || []; + const statuses = body.statuses || []; + const homeUniversities = body.homeUniversities || []; const percentileInput = body.percentileInput || ''; const sortBy = body.sortBy || 'last_rank'; const sortOrder = body.sortOrder || 'desc'; @@ -40,6 +42,8 @@ export async function POST(request: NextRequest) { cutoff_score: '92.6268989', last_rank: '1000', total_admitted: 60, + status: 'Government', + home_university: 'Mumbai University', created: new Date().toISOString(), updated: new Date().toISOString() }; @@ -56,6 +60,8 @@ export async function POST(request: NextRequest) { cutoff_score: '91.6268989', last_rank: '1500', total_admitted: 45, + status: 'Government Autonomous', + home_university: 'Savitribai Phule Pune University', created: new Date().toISOString(), updated: new Date().toISOString() }; @@ -71,6 +77,8 @@ export async function POST(request: NextRequest) { cutoff_score: String((99.5 - (i * 0.02)).toFixed(7)), // More varied precise decimal values last_rank: String(1000 + (i * 50)), total_admitted: 60 + (i % 20), + status: ['Government', 'Government Autonomous', 'Un-Aided', 'University', 'Deemed University Autonomous'][i % 5], + home_university: ['Mumbai University', 'Savitribai Phule Pune University', 'Shivaji University', 'Dr. Babasaheb Ambedkar Marathwada University', 'Rashtrasant Tukadoji Maharaj Nagpur University'][i % 5], created: new Date().toISOString(), updated: new Date().toISOString() }; @@ -94,6 +102,21 @@ export async function POST(request: NextRequest) { mockData = mockData.filter((item: any) => seatAllocations.includes(item.seat_allocation_section)); } + // Apply status filter if provided + if (statuses && statuses.length > 0) { + mockData = mockData.filter((item: any) => statuses.includes(item.status)); + } + + // Apply home university filter if provided + if (homeUniversities && homeUniversities.length > 0) { + mockData = mockData.filter((item: any) => homeUniversities.includes(item.home_university)); + } + + // Apply courses filter if provided + if (courses && courses.length > 0) { + mockData = mockData.filter((item: any) => courses.includes(item.course_name)); + } + // Apply percentile filter if provided if (percentileInput && !isNaN(parseFloat(percentileInput))) { const targetPercentile = parseFloat(percentileInput); @@ -176,6 +199,16 @@ export async function POST(request: NextRequest) { filterParts.push(`(${seatFilter})`); } + if (statuses && Array.isArray(statuses) && statuses.length > 0) { + const statusFilter = statuses.map((status: string) => `status = "${status}"`).join(' || '); + filterParts.push(`(${statusFilter})`); + } + + if (homeUniversities && Array.isArray(homeUniversities) && homeUniversities.length > 0) { + const homeUniversityFilter = homeUniversities.map((uni: string) => `home_university = "${uni}"`).join(' || '); + filterParts.push(`(${homeUniversityFilter})`); + } + // Percentile-based filtering (-1 range only) - filtering cutoff_score directly if (percentileInput && !isNaN(parseFloat(percentileInput))) { const targetPercentile = parseFloat(percentileInput); @@ -209,7 +242,7 @@ export async function POST(request: NextRequest) { }); try { - const result = await pb.collection('2024_mht_cet_round_one_cutoffs').getList( + const result = await pb.collection('2024_mht_cet_round_one_cutoffs_duplicate').getList( page, perPage, { @@ -258,6 +291,8 @@ export async function POST(request: NextRequest) { cutoff_score: '97.989925', last_rank: '500', total_admitted: 60, + status: 'Government', + home_university: 'Mumbai University', created: new Date().toISOString(), updated: new Date().toISOString() }; @@ -274,6 +309,8 @@ export async function POST(request: NextRequest) { cutoff_score: '92.6268989', last_rank: '1000', total_admitted: 60, + status: 'Government Autonomous', + home_university: 'Savitribai Phule Pune University', created: new Date().toISOString(), updated: new Date().toISOString() }; @@ -290,6 +327,8 @@ export async function POST(request: NextRequest) { cutoff_score: '91.6268989', last_rank: '1500', total_admitted: 45, + status: 'Un-Aided', + home_university: 'Shivaji University', created: new Date().toISOString(), updated: new Date().toISOString() }; @@ -305,6 +344,8 @@ export async function POST(request: NextRequest) { cutoff_score: String((99.5 - (i * 0.01)).toFixed(7)), // More realistic percentile values last_rank: String(1000 + (i * 50)), total_admitted: 60 + (i % 20), + status: ['Government', 'Government Autonomous', 'Un-Aided', 'University', 'Deemed University Autonomous'][i % 5], + home_university: ['Mumbai University', 'Savitribai Phule Pune University', 'Shivaji University', 'Dr. Babasaheb Ambedkar Marathwada University', 'Rashtrasant Tukadoji Maharaj Nagpur University'][i % 5], created: new Date().toISOString(), updated: new Date().toISOString() }; @@ -318,6 +359,31 @@ export async function POST(request: NextRequest) { ); } + // Apply category filter to mock data if provided + if (categories && categories.length > 0) { + mockData = mockData.filter((item: any) => categories.includes(item.category)); + } + + // Apply seat allocation filter to mock data if provided + if (seatAllocations && seatAllocations.length > 0) { + mockData = mockData.filter((item: any) => seatAllocations.includes(item.seat_allocation_section)); + } + + // Apply status filter to mock data if provided + if (statuses && statuses.length > 0) { + mockData = mockData.filter((item: any) => statuses.includes(item.status)); + } + + // Apply home university filter to mock data if provided + if (homeUniversities && homeUniversities.length > 0) { + mockData = mockData.filter((item: any) => homeUniversities.includes(item.home_university)); + } + + // Apply courses filter to mock data if provided + if (courses && courses.length > 0) { + mockData = mockData.filter((item: any) => courses.includes(item.course_name)); + } + // Apply percentile filter to mock data if provided if (percentileInput && !isNaN(parseFloat(percentileInput))) { const targetPercentile = parseFloat(percentileInput); diff --git a/app/mht-cet/state-cutoffs/page.tsx b/app/mht-cet/state-cutoffs/page.tsx index 1e10462..2f5bb3f 100644 --- a/app/mht-cet/state-cutoffs/page.tsx +++ b/app/mht-cet/state-cutoffs/page.tsx @@ -1,7 +1,6 @@ 'use client'; import { useState, useEffect, useMemo, useCallback, useRef } from 'react'; -import { getPocketBaseClient } from '@/lib/pocketbaseClient'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -57,6 +56,7 @@ import { AccordionTrigger, } from "@/components/ui/accordion"; import { BookUser, Building, GraduationCap, Info, Lightbulb, MapPin, ShieldCheck, Users, Video } from 'lucide-react'; +import CategoryFlowChart from '@/components/CategoryFlowChart'; // Types interface CutoffRecord { @@ -70,6 +70,8 @@ interface CutoffRecord { cutoff_score: string; last_rank: string; total_admitted: number; + status: string; + home_university: string; created: string; updated: string; } @@ -77,8 +79,9 @@ interface CutoffRecord { interface FilterState { search: string; categories: string[]; - seatAllocations: string[]; courses: string[]; + statuses: string[]; + homeUniversities: string[]; percentileInput: string; sortBy: string; sortOrder: 'asc' | 'desc'; @@ -87,8 +90,9 @@ interface FilterState { interface PendingFilters { search: string; categories: string[]; - seatAllocations: string[]; courses: string[]; + statuses: string[]; + homeUniversities: string[]; percentileInput: string; } @@ -260,11 +264,53 @@ const COURSE_GROUPS = { ] }; -const SEAT_ALLOCATION_OPTIONS = [ - { value: 'OTHER_TO_OTHER', label: 'Other to Other University' }, - { value: 'HOME_TO_OTHER', label: 'Home to Other University' }, - { value: 'HOME_TO_HOME', label: 'Home to Home University' }, - { value: 'STATE_LEVEL', label: 'State Level Seats' } +const STATUS_OPTIONS = [ + { value: 'Deemed University Autonomous', label: 'Deemed University Autonomous' }, + { value: 'Government', label: 'Government' }, + { value: 'Government Autonomous', label: 'Government Autonomous' }, + { value: 'Government-Aided Autonomous', label: 'Government-Aided Autonomous' }, + { value: 'Un-Aided', label: 'Un-Aided' }, + { value: 'Un-Aided Autonomous', label: 'Un-Aided Autonomous' }, + { value: 'Un-Aided Autonomous Linguistic Minority - Gujarathi', label: 'Un-Aided Autonomous Linguistic Minority - Gujarathi' }, + { value: 'Un-Aided Autonomous Linguistic Minority - Gujarathi(Jain)', label: 'Un-Aided Autonomous Linguistic Minority - Gujarathi(Jain)' }, + { value: 'Un-Aided Autonomous Linguistic Minority - Hindi', label: 'Un-Aided Autonomous Linguistic Minority - Hindi' }, + { value: 'Un-Aided Autonomous Linguistic Minority - Malyalam', label: 'Un-Aided Autonomous Linguistic Minority - Malyalam' }, + { value: 'Un-Aided Autonomous Linguistic Minority - Sindhi', label: 'Un-Aided Autonomous Linguistic Minority - Sindhi' }, + { value: 'Un-Aided Autonomous Linguistic Minority - Tamil', label: 'Un-Aided Autonomous Linguistic Minority - Tamil' }, + { value: 'Un-Aided Autonomous Religious Minority - Christian', label: 'Un-Aided Autonomous Religious Minority - Christian' }, + { value: 'Un-Aided Autonomous Religious Minority - Jain', label: 'Un-Aided Autonomous Religious Minority - Jain' }, + { value: 'Un-Aided Linguistic Minority - Gujar', label: 'Un-Aided Linguistic Minority - Gujar' }, + { value: 'Un-Aided Linguistic Minority - Gujarathi', label: 'Un-Aided Linguistic Minority - Gujarathi' }, + { value: 'Un-Aided Linguistic Minority - Hindi', label: 'Un-Aided Linguistic Minority - Hindi' }, + { value: 'Un-Aided Linguistic Minority - Malyalam', label: 'Un-Aided Linguistic Minority - Malyalam' }, + { value: 'Un-Aided Linguistic Minority - Punjabi', label: 'Un-Aided Linguistic Minority - Punjabi' }, + { value: 'Un-Aided Linguistic Minority - Sindhi', label: 'Un-Aided Linguistic Minority - Sindhi' }, + { value: 'Un-Aided Religious Minority - Christian', label: 'Un-Aided Religious Minority - Christian' }, + { value: 'Un-Aided Religious Minority - Jain', label: 'Un-Aided Religious Minority - Jain' }, + { value: 'Un-Aided Religious Minority - Muslim', label: 'Un-Aided Religious Minority - Muslim' }, + { value: 'Un-Aided Religious Minority - Roman Catholics', label: 'Un-Aided Religious Minority - Roman Catholics' }, + { value: 'University', label: 'University' }, + { value: 'University Autonomous', label: 'University Autonomous' }, + { value: 'University Department', label: 'University Department' }, + { value: 'University Managed (Un-Aided)', label: 'University Managed (Un-Aided)' }, + { value: 'University Managed Autonomous', label: 'University Managed Autonomous' } +]; + +const HOME_UNIVERSITY_OPTIONS = [ + { value: 'Autonomous Institute', label: 'Autonomous Institute' }, + { value: 'Deemed to be University', label: 'Deemed to be University' }, + { value: 'Dr. Babasaheb Ambedkar Marathwada University', label: 'Dr. Babasaheb Ambedkar Marathwada University' }, + { value: 'Dr. Babasaheb Ambedkar Technological University Lonere', label: 'Dr. Babasaheb Ambedkar Technological University Lonere' }, + { value: 'Gondwana University', label: 'Gondwana University' }, + { value: 'Kavayitri Bahinabai Chaudhari North Maharashtra University Jalgaon', label: 'Kavayitri Bahinabai Chaudhari North Maharashtra University Jalgaon' }, + { value: 'Mumbai University', label: 'Mumbai University' }, + { value: 'Punyashlok Ahilyadevi Holkar Solapur University', label: 'Punyashlok Ahilyadevi Holkar Solapur University' }, + { value: 'Rashtrasant Tukadoji Maharaj Nagpur University', label: 'Rashtrasant Tukadoji Maharaj Nagpur University' }, + { value: 'SNDT Women s University', label: 'SNDT Women s University' }, + { value: 'Sant Gadge Baba Amravati University', label: 'Sant Gadge Baba Amravati University' }, + { value: 'Savitribai Phule Pune University', label: 'Savitribai Phule Pune University' }, + { value: 'Shivaji University', label: 'Shivaji University' }, + { value: 'Swami Ramanand Teerth Marathwada University Nanded', label: 'Swami Ramanand Teerth Marathwada University Nanded' } ]; const ITEMS_PER_PAGE_OPTIONS = [10, 25, 50, 100, 200]; @@ -318,8 +364,9 @@ export default function StateCutoffsPage() { const [filters, setFilters] = useState({ search: '', categories: [], - seatAllocations: [], courses: [], + statuses: [], + homeUniversities: [], percentileInput: '', sortBy: 'last_rank', sortOrder: 'desc' @@ -329,8 +376,9 @@ export default function StateCutoffsPage() { const [pendingFilters, setPendingFilters] = useState({ search: '', categories: [], - seatAllocations: [], courses: [], + statuses: [], + homeUniversities: [], percentileInput: '' }); @@ -344,6 +392,7 @@ export default function StateCutoffsPage() { const currentRequestParamsRef = useRef(''); const requestIdRef = useRef(0); const lastPaginationClickRef = useRef(0); + const prevItemsPerPageRef = useRef(itemsPerPage); // Table state const [sorting, setSorting] = useState([{ id: 'cutoff_score', desc: true }]) @@ -355,6 +404,11 @@ export default function StateCutoffsPage() { const memoizedRecords = useMemo(() => records, [records]); const memoizedTotalItems = useMemo(() => totalItems, [totalItems]); + // Debug useEffect to track currentPage changes + useEffect(() => { + console.log('currentPage changed to:', currentPage); + }, [currentPage]); + // Define columns for the enhanced table with Abel font and swapped score/percentile const columns: ColumnDef[] = useMemo( () => [ @@ -487,41 +541,6 @@ export default function StateCutoffsPage() { ), size: 100, }, - { - accessorKey: "seat_allocation_section", - header: ({ column }) => { - return ( - - ) - }, - cell: ({ row }) => { - const value = row.getValue("seat_allocation_section") as string; - const option = SEAT_ALLOCATION_OPTIONS.find(s => s.value === value); - const label = option?.label || value; - return ( - - - -
- {label} -
-
- -

{label}

-
-
-
- ); - }, - size: 150, - }, { accessorKey: "total_admitted", header: ({ column }) => { @@ -615,6 +634,66 @@ export default function StateCutoffsPage() { ), size: 110, }, + { + accessorKey: "status", + header: ({ column }) => { + return ( + + ) + }, + cell: ({ row }) => ( + + + +
+ {row.getValue("status")} +
+
+ +

{row.getValue("status")}

+
+
+
+ ), + size: 180, + }, + { + accessorKey: "home_university", + header: ({ column }) => { + return ( + + ) + }, + cell: ({ row }) => ( + + + +
+ {row.getValue("home_university")} +
+
+ +

{row.getValue("home_university")}

+
+
+
+ ), + size: 250, + }, ], [] ); @@ -651,8 +730,9 @@ export default function StateCutoffsPage() { const filtersChanged = ( pendingFilters.search !== filters.search || JSON.stringify(pendingFilters.categories) !== JSON.stringify(filters.categories) || - JSON.stringify(pendingFilters.seatAllocations) !== JSON.stringify(filters.seatAllocations) || JSON.stringify(pendingFilters.courses) !== JSON.stringify(filters.courses) || + JSON.stringify(pendingFilters.statuses) !== JSON.stringify(filters.statuses) || + JSON.stringify(pendingFilters.homeUniversities) !== JSON.stringify(filters.homeUniversities) || pendingFilters.percentileInput !== filters.percentileInput ); setHasUnsavedChanges(filtersChanged); @@ -660,10 +740,11 @@ export default function StateCutoffsPage() { useEffect(() => { table.setPageSize(itemsPerPage); - // Reset to first page when items per page changes - if (currentPage > 1) { + // Reset to first page when items per page changes (but not on currentPage changes) + if (prevItemsPerPageRef.current !== itemsPerPage && currentPage > 1) { setCurrentPage(1); } + prevItemsPerPageRef.current = itemsPerPage; }, [itemsPerPage, table, currentPage]); // Ensure current page is valid when total items change @@ -688,14 +769,16 @@ export default function StateCutoffsPage() { } console.log('Fetching records for page:', currentPage, 'with filters:', debouncedFilters); + console.log('Total items:', memoizedTotalItems, 'Items per page:', itemsPerPage); const requestBody = { page: currentPage, perPage: itemsPerPage, search: debouncedFilters.search, categories: debouncedFilters.categories, - seatAllocations: debouncedFilters.seatAllocations, courses: debouncedFilters.courses, + statuses: debouncedFilters.statuses, + homeUniversities: debouncedFilters.homeUniversities, percentileInput: debouncedFilters.percentileInput, sortBy: debouncedFilters.sortBy, sortOrder: debouncedFilters.sortOrder, @@ -704,7 +787,7 @@ export default function StateCutoffsPage() { const cacheKey = JSON.stringify(requestBody); // Check if this is the same request type (only different pagination) - const currentRequestKey = `${debouncedFilters.search}-${debouncedFilters.categories.join(',')}-${debouncedFilters.seatAllocations.join(',')}-${debouncedFilters.courses.join(',')}-${debouncedFilters.percentileInput}`; + const currentRequestKey = `${debouncedFilters.search}-${debouncedFilters.categories.join(',')}-${debouncedFilters.courses.join(',')}-${debouncedFilters.statuses.join(',')}-${debouncedFilters.homeUniversities.join(',')}-${debouncedFilters.percentileInput}`; const isOnlyPaginationChange = currentRequestParamsRef.current === currentRequestKey; // Check cache first @@ -737,6 +820,7 @@ export default function StateCutoffsPage() { try { console.log(`Making API POST request ${requestId} with body:`, requestBody); + console.log('Critical pagination values:', { currentPage, itemsPerPage, memoizedTotalItems }); const response = await fetch(`/api/mht-cet/state-cutoffs`, { method: 'POST', @@ -770,7 +854,8 @@ export default function StateCutoffsPage() { throw new Error(result.error || 'Failed to fetch data'); } - console.log(`Request ${requestId} completed successfully`); + console.log(`Request ${requestId} completed successfully with`, result.data.length, 'records on page', result.page, 'of', result.totalPages); + console.log('API Response:', { success: result.success, totalItems: result.totalItems, page: result.page, perPage: result.perPage }); // Cache the result (limit cache size to prevent memory issues) if (cacheRef.current.size > 50) { @@ -814,7 +899,7 @@ export default function StateCutoffsPage() { setIsRequestActive(false); } } - }, [currentPage, itemsPerPage, debouncedFilters]); + }, [currentPage, itemsPerPage, debouncedFilters, memoizedTotalItems]); useEffect(() => { // Clear any pending pagination timeout @@ -823,6 +908,7 @@ export default function StateCutoffsPage() { } // Debounce pagination requests to prevent rapid-fire API calls + console.log('useEffect triggered - debouncing fetch for current page:', currentPage); paginationTimeoutRef.current = setTimeout(() => { fetchRecords(); }, 150); // 150ms debounce for pagination @@ -832,7 +918,7 @@ export default function StateCutoffsPage() { clearTimeout(paginationTimeoutRef.current); } }; - }, [fetchRecords]); + }, [fetchRecords, currentPage]); // Cleanup effect useEffect(() => { @@ -855,21 +941,30 @@ export default function StateCutoffsPage() { })); }, []); - const handleSeatAllocationToggle = useCallback((seatAllocation: string) => { + const handleCourseToggle = useCallback((course: string) => { setPendingFilters(prev => ({ ...prev, - seatAllocations: prev.seatAllocations.includes(seatAllocation) - ? prev.seatAllocations.filter(s => s !== seatAllocation) - : [...prev.seatAllocations, seatAllocation] + courses: prev.courses.includes(course) + ? prev.courses.filter(c => c !== course) + : [...prev.courses, course] })); }, []); - const handleCourseToggle = useCallback((course: string) => { + const handleStatusToggle = useCallback((status: string) => { setPendingFilters(prev => ({ ...prev, - courses: prev.courses.includes(course) - ? prev.courses.filter(c => c !== course) - : [...prev.courses, course] + statuses: prev.statuses.includes(status) + ? prev.statuses.filter(s => s !== status) + : [...prev.statuses, status] + })); + }, []); + + const handleHomeUniversityToggle = useCallback((homeUniversity: string) => { + setPendingFilters(prev => ({ + ...prev, + homeUniversities: prev.homeUniversities.includes(homeUniversity) + ? prev.homeUniversities.filter(h => h !== homeUniversity) + : [...prev.homeUniversities, homeUniversity] })); }, []); @@ -881,8 +976,9 @@ export default function StateCutoffsPage() { ...prev, search: pendingFilters.search, categories: pendingFilters.categories, - seatAllocations: pendingFilters.seatAllocations, courses: pendingFilters.courses, + statuses: pendingFilters.statuses, + homeUniversities: pendingFilters.homeUniversities, percentileInput: pendingFilters.percentileInput })); setSorting([{ id: 'cutoff_score', desc: true }]); // Ensure default sorting @@ -894,8 +990,9 @@ export default function StateCutoffsPage() { const clearedFilters = { search: '', categories: [], - seatAllocations: [], courses: [], + statuses: [], + homeUniversities: [], percentileInput: '' }; // Clear cache when filters are cleared @@ -915,17 +1012,29 @@ export default function StateCutoffsPage() { const now = Date.now(); const timeSinceLastClick = now - lastPaginationClickRef.current; - // Throttle pagination clicks to prevent rapid requests - if (timeSinceLastClick < 200) { // 200ms throttle - return; - } - - lastPaginationClickRef.current = now; + console.log(`handlePageChange called with newPage: ${newPage}, currentPage: ${currentPage}, memoizedTotalItems: ${memoizedTotalItems}, itemsPerPage: ${itemsPerPage}`); + // Always allow the page change, just throttle the timing if (newPage >= 1 && newPage <= Math.ceil(memoizedTotalItems / itemsPerPage)) { + console.log(`Setting currentPage to: ${newPage} (was: ${currentPage})`); setCurrentPage(newPage); + + // Force immediate debug of what happens after state update + setTimeout(() => { + console.log('After state update, currentPage should be:', newPage); + }, 100); + + // Update the timestamp to prevent rapid successive clicks + lastPaginationClickRef.current = now; + } else { + console.log(`Page change rejected - out of bounds. Valid range: 1 to ${Math.ceil(memoizedTotalItems / itemsPerPage)}`); } - }, [memoizedTotalItems, itemsPerPage]); + }, [memoizedTotalItems, itemsPerPage, currentPage]); + + // Debug useEffect to track currentPage changes + useEffect(() => { + console.log('currentPage changed to:', currentPage); + }, [currentPage]); return (
@@ -1012,15 +1121,15 @@ export default function StateCutoffsPage() {
-
+
{/* Enhanced Category Filter */} - - -
+ +
-

Select Categories

+

Select Categories

- -
+ +
{Object.entries(CATEGORY_GROUPS).map(([group, categories]) => ( -
-
- +
+
+
{categories.map((category) => ( -
+
handleCategoryToggle(category)} + className="data-[state=checked]:bg-blue-600 data-[state=checked]:border-blue-600" /> -
))}
-
))}
@@ -1097,11 +1206,11 @@ export default function StateCutoffsPage() { {/* Enhanced Course Filter */} - - -
+ +
-

Select Courses

+

Select Courses

- -
+ +
{Object.entries(COURSE_GROUPS).map(([group, courses]) => ( -
-
- +
+
+
-
+
{courses.map((course) => ( -
+
handleCourseToggle(course)} - className="mt-1" + className="mt-1 data-[state=checked]:bg-green-600 data-[state=checked]:border-green-600" /> -
))}
-
))}
@@ -1176,33 +1284,48 @@ export default function StateCutoffsPage() { - {/* Seat Allocation Filter */} + {/* Enhanced Status Filter */} - - - -
- {SEAT_ALLOCATION_OPTIONS.map((option) => ( -
+ +
+
+

Select Status

+ +
+
+ +
+ {STATUS_OPTIONS.map((option) => ( +
handleSeatAllocationToggle(option.value)} - className="mt-1" + checked={pendingFilters.statuses.includes(option.value)} + onCheckedChange={() => handleStatusToggle(option.value)} + className="mt-1 data-[state=checked]:bg-orange-600 data-[state=checked]:border-orange-600" /> -
@@ -1212,12 +1335,63 @@ export default function StateCutoffsPage() { - {/* Search and Clear Buttons */} + {/* Enhanced Home University Filter */} + + + + + +
+
+

Select Home University

+ +
+
+ +
+ {HOME_UNIVERSITY_OPTIONS.map((option) => ( +
+ handleHomeUniversityToggle(option.value)} + className="mt-1 data-[state=checked]:bg-teal-600 data-[state=checked]:border-teal-600" + /> + +
+ ))} +
+
+
+
+ + {/* Enhanced Search and Clear Buttons */}
@@ -1264,75 +1438,77 @@ export default function StateCutoffsPage() { {/* Active Filters Display */} - {(filters.percentileInput || filters.categories.length > 0 || filters.seatAllocations.length > 0 || filters.courses.length > 0) && ( - - -
- -

Active Filters

+ {(filters.percentileInput || filters.categories.length > 0 || filters.courses.length > 0 || filters.statuses.length > 0 || filters.homeUniversities.length > 0) && ( + + +
+ +

Active Filters

-
+
{filters.percentileInput && ( - + Target: {filters.percentileInput}% )} {filters.categories.length > 0 && ( -
- +
+ Categories ({filters.categories.length}) - {filters.categories.slice(0, 2).map((category, index) => ( - + {filters.categories.map((category, index) => ( + {category} ))} - {filters.categories.length > 2 && ( - - +{filters.categories.length - 2} more - - )}
)} - {filters.seatAllocations.length > 0 && ( -
- - Seats ({filters.seatAllocations.length}) + {filters.courses.length > 0 && ( +
+ + Courses ({filters.courses.length}) - {filters.seatAllocations.slice(0, 1).map((allocation, index) => { - const option = SEAT_ALLOCATION_OPTIONS.find(s => s.value === allocation); - const label = option?.label || allocation; - return ( - - {label} - - ); - })} - {filters.seatAllocations.length > 1 && ( - - +{filters.seatAllocations.length - 1} more + {filters.courses.map((course, index) => ( + + {course.length > 20 ? `${course.substring(0, 20)}...` : course} - )} + ))}
)} - {filters.courses.length > 0 && ( -
- - Courses ({filters.courses.length}) + {filters.statuses.length > 0 && ( +
+ + Status ({filters.statuses.length}) - {filters.courses.slice(0, 1).map((course, index) => ( - - {course.length > 20 ? `${course.substring(0, 20)}...` : course} + {filters.statuses.map((status, index) => ( + + {status.length > 25 ? `${status.substring(0, 25)}...` : status} ))} - {filters.courses.length > 1 && ( - - +{filters.courses.length - 1} more +
+ )} + {filters.homeUniversities.length > 0 && ( +
+ + Universities ({filters.homeUniversities.length}) + + {filters.homeUniversities.map((university, index) => ( + + {university.length > 30 ? `${university.substring(0, 30)}...` : university} - )} + ))}
)}
+
+ +
)} @@ -1351,6 +1527,14 @@ export default function StateCutoffsPage() { {filters.categories.length} categories
+
+ {filters.statuses.length} + statuses +
+
+ {filters.homeUniversities.length} + universities +
{filters.percentileInput ? '-1%' : 'All'} range @@ -1525,7 +1709,10 @@ export default function StateCutoffsPage() { -
{/* Filters */} @@ -1091,7 +1045,7 @@ export default function StateCutoffsPage() { {/* Percentile Input */}
{isSearching || loading ? ( <> @@ -1536,7 +1490,7 @@ export default function StateCutoffsPage() { universities
- {filters.percentileInput ? '-1%' : 'All'} + {filters.percentileInput ? '-10%' : 'All'} range
@@ -1792,7 +1746,7 @@ export default function StateCutoffsPage() {
1

Enter Your Percentile

-

Type your MHT-CET percentile to see colleges in a -1% range.

+

Type your MHT-CET percentile to see colleges in a -10% range.

@@ -1812,7 +1766,7 @@ export default function StateCutoffsPage() {

- Pro Tip: The -1% range shows realistic options, helping you find colleges where you have a strong chance of admission. + Pro Tip: The -10% range shows realistic options, helping you find colleges where you have a strong chance of admission.

@@ -1897,6 +1851,17 @@ export default function StateCutoffsPage() {
+ + +
+ + Video Explanation +
+
+ + + +