import React, { useEffect, useState } from 'react'; import { api } from '../api'; import type { ClassItem, SyncStatus } from '../api'; import { openClass } from './NavHistoryBar'; export const ClassesTab: React.FC = () => { const [classes, setClasses] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [pageSize] = useState(15); const [search, setSearch] = useState(''); const [systemFilter, setSystemFilter] = useState(undefined); const [studyingOnly, setStudyingOnly] = useState(true); const [loading, setLoading] = useState(true); // Sync state const [syncStatus, setSyncStatus] = useState(null); const fetchClasses = async () => { try { setLoading(true); const res = await api.getClasses({ page, pageSize, q: search || undefined, systemRkId: systemFilter, studyingOnly, }); setClasses(res.data); setTotal(res.total); } catch (err) { console.error(err); } finally { setLoading(false); } }; const fetchSyncStatus = async () => { try { const status = await api.getClassesSyncStatus(); setSyncStatus(status); return status.running; } catch (err) { console.error('Lỗi khi lấy status sync:', err); return false; } }; useEffect(() => { fetchClasses(); }, [page, search, systemFilter, studyingOnly]); // Check sync status on load and start polling if running useEffect(() => { fetchSyncStatus(); }, []); // Sync polling logic useEffect(() => { let timer: any; if (syncStatus?.running) { timer = setInterval(async () => { const isRunning = await fetchSyncStatus(); if (!isRunning) { clearInterval(timer); fetchClasses(); // Reload data when sync completes } }, 2000); } return () => { if (timer) clearInterval(timer); }; }, [syncStatus?.running]); const handleStartSync = async () => { try { await api.startClassesSync(); // Set local state to running to trigger useEffect poller setSyncStatus({ running: true, done: false, total: 0, synced: 0, updatedAt: Date.now() / 1000 }); } catch (err: any) { alert(err.message || 'Không thể bắt đầu đồng bộ lớp học'); } }; const handleToggleStudying = async (cItem: ClassItem) => { const nextVal = !cItem.isStudying; try { // Optimistic update setClasses(prev => prev.map(c => c.rkId === cItem.rkId ? { ...c, isStudying: nextVal } : c)); await api.updateClassStudying(cItem.rkId, nextVal); } catch (err: any) { // Revert if error setClasses(prev => prev.map(c => c.rkId === cItem.rkId ? { ...c, isStudying: !nextVal } : c)); alert(err.message || 'Không thể cập nhật trạng thái lớp học'); } }; // Tính toán % tiến trình sync const syncPercent = syncStatus && syncStatus.total > 0 ? Math.round((syncStatus.synced / syncStatus.total) * 100) : 0; return (

Quản Lý Lớp Học

Đồng bộ dữ liệu lớp học và sinh viên từ hệ thống chính

{/* Sync Status Banner */} {syncStatus && (syncStatus.running || syncStatus.done || syncStatus.error) && (
{syncStatus.running &&
} {syncStatus.running && `Đang đồng bộ lớp học... (${syncPercent}%)`} {syncStatus.done && '🎉 Đồng bộ lớp học hoàn tất!'} {syncStatus.error && `❌ Đồng bộ thất bại: ${syncStatus.error}`}
Đã đồng bộ: {syncStatus.synced} / {syncStatus.total} lớp
{syncStatus.running && (
)}
Cập nhật lần cuối: {new Date(syncStatus.updatedAt * 1000).toLocaleString()} {syncStatus.running && Vui lòng không đóng trình duyệt}
)} {/* Control bar filters */}
{ setSearch(e.target.value); setPage(1); }} /> 🔍
{/* Classes list table */}
{loading ? (

Đang tải danh sách lớp học...

) : classes.length === 0 ? (
🏫

Không tìm thấy lớp học nào

Hãy thử thay đổi bộ lọc hoặc bấm nút "Đồng bộ Lớp học" để kéo dữ liệu mới.

) : ( {classes.map(cl => ( ))}
Mã lớp / ID Tên Lớp học Phân hệ Môn học của lớp Sinh viên Đang dạy Hành động
{cl.classCode}
ID: {cl.rkId}
openClass('classes', cl.rkId, cl.name)} title="Mở Không gian làm việc của lớp" > {cl.name}
💼 {cl.specializeName || 'Chưa có chuyên ngành'}
📍 {cl.systemName || `Hệ thống ${cl.systemRkId || 'Khác'}`}
{cl.courses && cl.courses.length > 0 ? ( cl.courses.map((co, i) => ( {co.courseName} )) ) : ( Chưa có môn học )}
👤 {cl.studentCount} SV
{cl.isStudying ? ( Đang học ) : ( Tạm dừng )}
)}
{/* Pagination control bar */} {!loading && classes.length > 0 && (
Hiển thị lớp thứ {((page - 1) * pageSize) + 1} đến {Math.min(page * pageSize, total)} trong tổng số {total} lớp học
Trang {page} / {Math.ceil(total / pageSize) || 1}
)}
); };