diff --git a/management/src/components/ExamRoomWorkspace.tsx b/management/src/components/ExamRoomWorkspace.tsx index 4395684..994672b 100644 --- a/management/src/components/ExamRoomWorkspace.tsx +++ b/management/src/components/ExamRoomWorkspace.tsx @@ -77,8 +77,7 @@ export const ExamRoomWorkspace: React.FC = ({ examId, onBack }) => { const [searchHits, setSearchHits] = useState<{ studentRkId: number; fullName: string; studentCode: string; email: string; classCodes: string }[]>([]); const [selectedPickerRkIds, setSelectedPickerRkIds] = useState([]); const [paperTitle, setPaperTitle] = useState(''); - const [packagePdf, setPackagePdf] = useState(null); - const [packageResources, setPackageResources] = useState([]); + const [packagePdfs, setPackagePdfs] = useState([]); const [creatingPackage, setCreatingPackage] = useState(false); const [sendSchedule, setSendSchedule] = useState(''); const [msg, setMsg] = useState(''); @@ -294,21 +293,35 @@ export const ExamRoomWorkspace: React.FC = ({ examId, onBack }) => { } }; - const createPackage = async () => { - if (!packagePdf) { - setErr('Chọn file PDF cho gói đề'); + const createPackagesBulk = async () => { + if (!packagePdfs.length) { + setErr('Chọn ít nhất một file PDF đề chính'); return; } setErr(''); setCreatingPackage(true); try { - const title = paperTitle.trim() || packagePdf.name.replace(/\.pdf$/i, '') || 'Gói đề'; - const resCount = packageResources.length; - await apiExam.uploadPackage(examId, title, packagePdf, packageResources); + const prefix = paperTitle.trim(); + const createdTitle = packagePdfs.length === 1 + ? (prefix || packagePdfs[0].name.replace(/\.pdf$/i, '') || 'Gói đề') + : ''; + let created = 0; + for (let i = 0; i < packagePdfs.length; i++) { + const pdf = packagePdfs[i]; + const baseName = pdf.name.replace(/\.pdf$/i, '') || 'Gói đề'; + const title = packagePdfs.length === 1 + ? (prefix || baseName) + : baseName; + await apiExam.uploadPackage(examId, title, pdf, []); + created++; + } setPaperTitle(''); - setPackagePdf(null); - setPackageResources([]); - setMsg(`Đã tạo gói đề "${title}" (1 PDF${resCount ? ` + ${resCount} tài nguyên` : ''})`); + setPackagePdfs([]); + setMsg( + created === 1 + ? `Đã tạo gói đề "${createdTitle}". Bổ sung tài nguyên bên dưới nếu cần.` + : `Đã tạo ${created} gói đề. Bổ sung tài nguyên cho từng gói bên dưới nếu cần.`, + ); await load(); } catch (e: any) { setErr(e?.message || 'Tạo gói đề thất bại'); @@ -317,6 +330,10 @@ export const ExamRoomWorkspace: React.FC = ({ examId, onBack }) => { } }; + const removePackagePdf = (index: number) => { + setPackagePdfs((prev) => prev.filter((_, i) => i !== index)); + }; + const uploadResource = async (paperId: number, e: React.ChangeEvent) => { const files = Array.from(e.target.files || []); if (!files.length) return; @@ -805,7 +822,7 @@ export const ExamRoomWorkspace: React.FC = ({ examId, onBack }) => {
Gói đề & gửi cho sinh viên

- Mỗi gói đề gồm 1 file PDF (đề chính) và tài nguyên kèm (ảnh, zip, doc…). Chia ngẫu nhiên sẽ gán cả gói cho từng sinh viên. + Chọn nhiều PDF đề chính cùng lúc để tạo gói hàng loạt. Sau đó bổ sung tài nguyên kèm (ảnh, zip, doc…) cho từng gói bên dưới.

{!canModifyPapers && (

@@ -816,44 +833,61 @@ export const ExamRoomWorkspace: React.FC = ({ examId, onBack }) => {

setPaperTitle(e.target.value)} /> - - {packageResources.length > 0 && ( -

- {packageResources.length} tài nguyên: {packageResources.map((f) => f.name).join(', ')} -

+ {packagePdfs.length > 0 && ( +
+

+ Đã chọn {packagePdfs.length} PDF — mỗi file sẽ thành 1 gói đề: +

+
    + {packagePdfs.map((f, i) => ( +
  • + 📄 {paperTitle.trim() && packagePdfs.length === 1 ? paperTitle.trim() : f.name.replace(/\.pdf$/i, '')} + +
  • + ))} +
+
)}
)} {papers.length === 0 ? ( -

Chưa có gói đề. Tạo gói gồm PDF + tài nguyên.

+

Chưa có gói đề. Chọn nhiều PDF để tạo hàng loạt.

) : (
{papers.map((p) => ( diff --git a/management/src/components/WorkspaceSeatingChart.tsx b/management/src/components/WorkspaceSeatingChart.tsx index 9816354..8fc354a 100644 --- a/management/src/components/WorkspaceSeatingChart.tsx +++ b/management/src/components/WorkspaceSeatingChart.tsx @@ -422,8 +422,8 @@ export const WorkspaceSeatingChart: React.FC = ({ )} - - {student.fullName.split(' ').pop()} + + {student.fullName} {student.studentCode} diff --git a/management/src/index.css b/management/src/index.css index ffe8bd1..5bc9467 100644 --- a/management/src/index.css +++ b/management/src/index.css @@ -2582,8 +2582,8 @@ input:checked + .slider:before { } .seating-grid-board { - --seating-cell-min-width: 136px; - --seating-cell-min-height: 156px; + --seating-cell-min-width: 148px; + --seating-cell-min-height: 176px; display: grid; gap: 10px; flex: 1; @@ -2646,15 +2646,19 @@ input:checked + .slider:before { } .seating-seat-name { - font-size: 0.8rem; + font-size: 0.72rem; font-weight: 700; margin-top: 2px; - white-space: nowrap; + white-space: normal; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; overflow: hidden; - text-overflow: ellipsis; + word-break: break-word; width: 100%; padding: 0 2px; - line-height: 1.2; + line-height: 1.25; + max-height: 3.75em; } .seating-seat-code {