From 83821b6b82cc7358e189cd2f36bf54b2b23c9ea8 Mon Sep 17 00:00:00 2001 From: PhuocNTB Date: Mon, 13 Jul 2026 15:23:48 +0700 Subject: [PATCH] fix mang --- management/src/components/ExamGridProctor.tsx | 325 ++++++++++++++++-- management/src/index.css | 2 +- 2 files changed, 300 insertions(+), 27 deletions(-) diff --git a/management/src/components/ExamGridProctor.tsx b/management/src/components/ExamGridProctor.tsx index e700a1b..1f08fa3 100644 --- a/management/src/components/ExamGridProctor.tsx +++ b/management/src/components/ExamGridProctor.tsx @@ -20,24 +20,72 @@ export const ExamGridProctor: React.FC = ({ const [searchQuery, setSearchQuery] = useState(''); const [onlyOnline, setOnlyOnline] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false); + const [zoomedStudent, setZoomedStudent] = useState(null); + const [pageSize, setPageSize] = useState(12); + const [currentPage, setCurrentPage] = useState(1); const wsRef = useRef(null); const subscribedRef = useRef>(new Set()); const gridContainerRef = useRef(null); + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + setZoomedStudent(null); + } + }; + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, []); + const studentIdsString = useMemo( () => students.map((s) => s.studentRkId).join(','), [students] ); - const onlineKey = useMemo(() => onlineIds.join(','), [onlineIds]); - const onlineIdsRef = useRef(onlineIds); - onlineIdsRef.current = onlineIds; + const filteredStudents = useMemo(() => { + return students.filter((s) => { + const matchesSearch = + s.fullName.toLowerCase().includes(searchQuery.toLowerCase()) || + s.studentCode.toLowerCase().includes(searchQuery.toLowerCase()); + const isOnline = onlineIds.includes(s.studentRkId); + const matchesOnlineFilter = !onlyOnline || isOnline; + return matchesSearch && matchesOnlineFilter; + }); + }, [students, onlineIds, searchQuery, onlyOnline]); + + const pagedStudents = useMemo(() => { + if (pageSize === 'all') return filteredStudents; + const start = (currentPage - 1) * pageSize; + return filteredStudents.slice(start, start + pageSize); + }, [filteredStudents, currentPage, pageSize]); + + const totalPages = useMemo(() => { + if (pageSize === 'all') return 1; + return Math.ceil(filteredStudents.length / pageSize) || 1; + }, [filteredStudents, pageSize]); + + useEffect(() => { + if (currentPage > totalPages) { + setCurrentPage(totalPages); + } + }, [totalPages, currentPage]); + + const visibleOnlineIds = useMemo(() => { + return pagedStudents + .map((s) => s.studentRkId) + .filter((id) => onlineIds.includes(id)); + }, [pagedStudents, onlineIds]); + + const visibleOnlineIdsRef = useRef(visibleOnlineIds); + visibleOnlineIdsRef.current = visibleOnlineIds; + + const onlineKey = useMemo(() => visibleOnlineIds.join(','), [visibleOnlineIds]); const syncSubscriptions = (ws: WebSocket) => { if (ws.readyState !== WebSocket.OPEN) return; - const currentOnlineIds = onlineIdsRef.current; - const target = new Set(currentOnlineIds); + const currentVisibleIds = visibleOnlineIdsRef.current; + const target = new Set(currentVisibleIds); const prev = subscribedRef.current; prev.forEach((id) => { @@ -46,7 +94,7 @@ export const ExamGridProctor: React.FC = ({ prev.delete(id); } }); - currentOnlineIds.forEach((id) => { + currentVisibleIds.forEach((id) => { if (!prev.has(id)) { ws.send(JSON.stringify({ event: 'teacher:subscribe', data: { studentId: id } })); prev.add(id); @@ -54,6 +102,11 @@ export const ExamGridProctor: React.FC = ({ }); }; + useEffect(() => { + const ws = wsRef.current; + if (ws) syncSubscriptions(ws); + }, [onlineKey]); + useEffect(() => { if (students.length === 0) return; @@ -144,11 +197,6 @@ export const ExamGridProctor: React.FC = ({ }; }, [studentIdsString]); - useEffect(() => { - const ws = wsRef.current; - if (ws) syncSubscriptions(ws); - }, [onlineKey]); - // Clean up frames when students are removed or go offline useEffect(() => { setScreenFrames((prev) => { @@ -177,17 +225,6 @@ export const ExamGridProctor: React.FC = ({ }); }, [onlineIds]); - const filteredStudents = useMemo(() => { - return students.filter((s) => { - const matchesSearch = - s.fullName.toLowerCase().includes(searchQuery.toLowerCase()) || - s.studentCode.toLowerCase().includes(searchQuery.toLowerCase()); - const isOnline = onlineIds.includes(s.studentRkId); - const matchesOnlineFilter = !onlyOnline || isOnline; - return matchesSearch && matchesOnlineFilter; - }); - }, [students, onlineIds, searchQuery, onlyOnline]); - const handleOpenChat = (s: ExamRoomStudent, e: React.MouseEvent) => { e.stopPropagation(); openStaffChat({ @@ -231,7 +268,7 @@ export const ExamGridProctor: React.FC = ({ 🔍 -