import React, { useEffect, useMemo, useState } from 'react'; import { apiFetchActiveSchedules, apiFetchClassSchedule, apiApplyScheduleTemplate, apiDeleteClassSchedule, apiFetchAllowedApps, apiSaveAllowedApps, apiFetchClassSessionLogs, apiFetchClasses } from '../api'; import { DEFAULT_ALLOWED_APPS } from '../constants'; import type { ClassItem, ClassScheduleItem, StudentSessionLogItem } from '../api'; import { LiveProctorModal } from './LiveProctorModal'; import { openClass } from './NavHistoryBar'; import { ScheduleEditor } from './ScheduleEditor'; import { LearningTimetable } from './LearningTimetable'; import { WEEK_DAYS, filterActiveClasses, countWeeklyShifts, classHasToday, schedulesByDay, getDayLabel, type ActiveClassItem, } from './learningUtils'; type ViewMode = 'list' | 'timetable'; export const LearningTab: React.FC = () => { const [activeClasses, setActiveClasses] = useState([]); const [loading, setLoading] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [dayFilter, setDayFilter] = useState(null); const [viewMode, setViewMode] = useState('list'); const [configClass, setConfigClass] = useState(null); const [schedules, setSchedules] = useState([]); const [allowedApps, setAllowedApps] = useState(''); const [monitorClass, setMonitorClass] = useState(null); const [studentsLogs, setStudentsLogs] = useState([]); const [showAddClassModal, setShowAddClassModal] = useState(false); const [allClasses, setAllClasses] = useState([]); const [searchClassQuery, setSearchClassQuery] = useState(''); const [proctorStudent, setProctorStudent] = useState<{ id: number; name: string; code: string } | null>(null); const loadActiveClasses = async () => { setLoading(true); try { const res = await apiFetchActiveSchedules(); setActiveClasses(res.data || []); } catch (err) { console.error('Failed to load active classes:', err); } finally { setLoading(false); } }; useEffect(() => { loadActiveClasses(); }, []); const filteredClasses = useMemo( () => filterActiveClasses(activeClasses, searchQuery, dayFilter), [activeClasses, searchQuery, dayFilter] ); const weeklyShifts = useMemo(() => countWeeklyShifts(activeClasses), [activeClasses]); const todayClassCount = useMemo( () => activeClasses.filter(classHasToday).length, [activeClasses] ); const handleOpenAddClass = async () => { setShowAddClassModal(true); try { const res = await apiFetchClasses({ page: 1, pageSize: 1000 }); setAllClasses(res.data || []); } catch (err) { console.error('Error loading classes:', err); } }; const handleActivateClass = async (cls: ClassItem) => { setShowAddClassModal(false); try { await apiApplyScheduleTemplate(cls.rkId); await apiSaveAllowedApps(cls.rkId, DEFAULT_ALLOWED_APPS); await loadActiveClasses(); handleOpenConfig(cls as ActiveClassItem); } catch (err) { alert('Không thể kích hoạt lớp: ' + err); } }; const handleOpenConfig = async (cls: ActiveClassItem) => { setConfigClass(cls); try { const schedRes = await apiFetchClassSchedule(cls.rkId); setSchedules(schedRes.data || []); const appRes = await apiFetchAllowedApps(cls.rkId); setAllowedApps(appRes.keywords || ''); } catch (err) { console.error('Error fetching config:', err); } }; const handleOpenMonitor = async (cls: ActiveClassItem) => { setMonitorClass(cls); setLoading(true); try { const logRes = await apiFetchClassSessionLogs(cls.rkId); setStudentsLogs(logRes.data || []); } catch (err) { console.error('Error fetching logs:', err); } finally { setLoading(false); } }; const reloadConfigSchedules = async () => { if (!configClass) return; try { const schedRes = await apiFetchClassSchedule(configClass.rkId); setSchedules(schedRes.data || []); loadActiveClasses(); } catch (err) { console.error('Error reloading schedules:', err); } }; const handleSaveApps = async () => { if (!configClass) return; try { await apiSaveAllowedApps(configClass.rkId, allowedApps); alert('Lưu cấu hình ứng dụng được phép thành công!'); loadActiveClasses(); } catch (err) { alert('Không thể lưu cấu hình app: ' + err); } }; const handleDeleteClass = async (cls: ActiveClassItem) => { if (window.confirm(`Bạn có chắc chắn muốn hủy cấu hình và dừng giám sát lớp ${cls.name}?`)) { try { await apiDeleteClassSchedule(cls.rkId); loadActiveClasses(); if (monitorClass?.rkId === cls.rkId) setMonitorClass(null); if (configClass?.rkId === cls.rkId) setConfigClass(null); } catch (err) { alert('Không thể hủy kích hoạt: ' + err); } } }; const handleOpenWorkspace = (cls: ActiveClassItem) => { openClass('learning', cls.rkId, cls.name); }; const formatSeconds = (totalSeconds: number) => { if (!totalSeconds) return '0s'; const hrs = Math.floor(totalSeconds / 3600); const mins = Math.floor((totalSeconds % 3600) / 60); const secs = totalSeconds % 60; const parts = []; if (hrs > 0) parts.push(`${hrs}h`); if (mins > 0) parts.push(`${mins}m`); if (secs > 0 || parts.length === 0) parts.push(`${secs}s`); return parts.join(' '); }; const filteredAddClasses = allClasses.filter(c => (c.name.toLowerCase().includes(searchClassQuery.toLowerCase()) || c.classCode.toLowerCase().includes(searchClassQuery.toLowerCase())) && !activeClasses.some(ac => ac.rkId === c.rkId) ); return (

Quản lý lịch học & Giám sát

Chọn lớp, lập lịch theo tuần và giám sát sinh viên — xem dạng danh sách hoặc thời khóa biểu

{activeClasses.length} Lớp đang giám sát
{weeklyShifts} Ca học / tuần
{todayClassCount} Lớp có ca hôm nay
{filteredClasses.length} Kết quả lọc
setSearchQuery(e.target.value)} />
{WEEK_DAYS.map((label, day) => ( ))}
{loading && (
🔄

Đang tải...

)} {!loading && activeClasses.length === 0 && (
📅

Chưa có lớp nào được tạo lịch học.

)} {!loading && activeClasses.length > 0 && filteredClasses.length === 0 && (
🔍

Không tìm thấy lớp phù hợp với bộ lọc hiện tại.

)} {!loading && filteredClasses.length > 0 && viewMode === 'timetable' && ( )} {!loading && filteredClasses.length > 0 && viewMode === 'list' && (
{filteredClasses.map(cls => { const byDay = schedulesByDay(cls); const today = classHasToday(cls); return (
{cls.classCode}

{cls.name}

{(cls.specializeName || cls.systemName) && (

{[cls.specializeName, cls.systemName].filter(Boolean).join(' · ')}

)}
{today && Hôm nay} {cls.studentCount != null && cls.studentCount > 0 && ( {cls.studentCount} SV )}
{byDay.size === 0 ? (

Chưa cấu hình ca học

) : ( Array.from(byDay.entries()).map(([day, shifts]) => (
{getDayLabel(day)}
{shifts.map((s, idx) => ( Ca{s.period || 1} {s.startTime && s.endTime ? `${s.startTime}–${s.endTime}` : ''} {s.courseName ? ` · ${s.courseName}` : ''} ))}
)) )}
{cls.allowedApps && (

App: {cls.allowedApps}

)}
); })}
)}
{configClass && (

Cấu hình lịch học & ứng dụng

{configClass.name} ({configClass.classCode})

Ca học trong tuần

App được phép chạy

Từ khóa ngăn chặn các ứng dụng ngoài luồng trên máy sinh viên.