import React, { useEffect, useMemo, useState } from 'react'; import { apiExam, type ExamRoomItem } from '../api'; import { openExam } from './NavHistoryBar'; import { useAuth } from '../auth/AuthContext'; function fmtTime(iso: string) { try { return new Date(iso).toLocaleString('vi-VN'); } catch { return iso; } } function statusMeta(st: string) { if (st === 'draft') return { text: 'Tạm thời', tone: 'muted' }; if (st === 'ready') return { text: 'Sẵn sàng', tone: 'info' }; if (st === 'active') return { text: 'Đang thi', tone: 'active' }; if (st === 'cancelled') return { text: 'Đã hủy', tone: 'warn' }; if (st === 'ended') return { text: 'Đã kết thúc', tone: 'muted' }; return { text: st, tone: 'muted' }; } function toLocalInput(iso?: string) { if (!iso) return ''; const d = new Date(iso); const pad = (n: number) => String(n).padStart(2, '0'); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`; } function localInputToISO(v: string) { if (!v) return ''; return new Date(v).toISOString(); } type RoomScope = 'mine' | 'all'; type IconProps = { size?: number; filled?: boolean }; const IconClipboard = ({ size = 22 }: IconProps) => ( ); const IconPlus = ({ size = 16 }: IconProps) => ( ); const IconStar = ({ size = 14, filled }: IconProps) => ( ); const IconSearch = ({ size = 16 }: IconProps) => ( ); const IconCalendar = ({ size = 13 }: IconProps) => ( ); const IconUsers = ({ size = 12 }: IconProps) => ( ); const IconOpen = ({ size = 14 }: IconProps) => ( ); const IconInbox = ({ size = 36 }: IconProps) => ( ); export const ExamsTab: React.FC = () => { const { staff } = useAuth(); const isSuperAdmin = staff?.email === 'phuocntb@rikkeiacademy.com'; const myStaffId = staff?.id ?? 0; const [rooms, setRooms] = useState([]); const [loading, setLoading] = useState(true); const [showCreate, setShowCreate] = useState(false); const [name, setName] = useState(''); const [start, setStart] = useState(''); const [end, setEnd] = useState(''); const [busy, setBusy] = useState(false); const [search, setSearch] = useState(''); const [roomScope, setRoomScope] = useState('mine'); const load = async () => { setLoading(true); try { const res = await apiExam.list(); setRooms(res.data); } catch (e) { console.error(e); } finally { setLoading(false); } }; useEffect(() => { load(); }, []); const scopedRooms = useMemo(() => { if (roomScope === 'all' || !myStaffId) return rooms; return rooms.filter((r) => Number(r.createdByStaffId) === myStaffId); }, [rooms, roomScope, myStaffId]); const stats = useMemo(() => { const active = scopedRooms.filter((r) => (r.displayStatus || r.status) === 'active').length; const upcoming = scopedRooms.filter((r) => { const st = r.displayStatus || r.status; return st === 'ready' || st === 'draft'; }).length; return { total: scopedRooms.length, active, upcoming }; }, [scopedRooms]); const filtered = useMemo(() => { const q = search.trim().toLowerCase(); if (!q) return scopedRooms; return scopedRooms.filter((r) => r.name.toLowerCase().includes(q)); }, [scopedRooms, search]); const canDeleteRoom = (r: ExamRoomItem) => isSuperAdmin || (myStaffId > 0 && Number(r.createdByStaffId) === myStaffId); const create = async () => { if (!name.trim() || !start || !end) return; setBusy(true); try { const room = await apiExam.create({ name: name.trim(), startTime: localInputToISO(start), endTime: localInputToISO(end), }); setShowCreate(false); setName(''); setStart(''); setEnd(''); await load(); openExam(room.id, room.name); } catch (e: any) { alert(e?.message || 'Lỗi'); } finally { setBusy(false); } }; const handleDeleteRoom = async (roomId: number, roomName: string) => { if ( window.confirm( `Bạn có chắc chắn muốn xóa hoàn toàn phòng thi "${roomName}" không? Hành động này sẽ xóa sạch dữ liệu phòng thi, các bài nộp, và không thể khôi phục.` ) ) { try { await apiExam.remove(roomId); alert('Đã xóa phòng thi thành công!'); await load(); } catch (err: any) { alert(err.message || 'Xóa phòng thi thất bại'); } } }; return (

Danh sách phòng thi

{roomScope === 'mine' ? 'Mặc định chỉ hiện phòng thi do bạn tạo — chuyển sang xem tất cả khi cần' : 'Đang xem toàn bộ phòng thi trong hệ thống'}

{stats.total} {roomScope === 'mine' ? 'Phòng của tôi' : 'Tổng phòng'}
{stats.active} Đang thi
{stats.upcoming} Sắp / tạm
setSearch(e.target.value)} />
{loading ? (

Đang tải danh sách phòng thi...

) : filtered.length === 0 ? (
{search.trim() ? : }

{search.trim() ? 'Không tìm thấy phòng thi phù hợp.' : roomScope === 'mine' ? 'Bạn chưa tạo phòng thi nào.' : 'Chưa có phòng thi — bấm Tạo phòng thi để bắt đầu.'}

{!search.trim() && (
{roomScope === 'mine' && ( )}
)}
) : (
{filtered.map((r) => { const st = statusMeta(r.displayStatus || r.status); const mine = myStaffId > 0 && Number(r.createdByStaffId) === myStaffId; const isActive = (r.displayStatus || r.status) === 'active'; return (

{r.name}

{fmtTime(r.startTime)} {fmtTime(r.endTime)}
{r.studentCount} SV · {r.paperCount} đề
{mine && Của tôi} {st.text}
{canDeleteRoom(r) && ( )}
); })}
)}
{showCreate && (
setShowCreate(false)}>
e.stopPropagation()}>

Tạo phòng thi mới

Đặt tên và khung giờ thi. Sau khi tạo sẽ mở workspace để cấu hình đề và sinh viên.

)}
); }; export { toLocalInput, localInputToISO, fmtTime };