fix add more student
This commit is contained in:
@@ -69,6 +69,7 @@ export const ExamRoomWorkspace: React.FC<Props> = ({ examId, onBack }) => {
|
||||
const [submissions, setSubmissions] = useState<ExamSubmission[]>([]);
|
||||
const [searchQ, setSearchQ] = useState('');
|
||||
const [searchHits, setSearchHits] = useState<{ studentRkId: number; fullName: string; studentCode: string; email: string; classCodes: string }[]>([]);
|
||||
const [selectedPickerRkIds, setSelectedPickerRkIds] = useState<number[]>([]);
|
||||
const [paperTitle, setPaperTitle] = useState('');
|
||||
const [packagePdf, setPackagePdf] = useState<File | null>(null);
|
||||
const [packageResources, setPackageResources] = useState<File[]>([]);
|
||||
@@ -210,6 +211,39 @@ export const ExamRoomWorkspace: React.FC<Props> = ({ examId, onBack }) => {
|
||||
await fetchOnline();
|
||||
};
|
||||
|
||||
const toggleHitSelection = (studentRkId: number) => {
|
||||
setSelectedPickerRkIds((prev) =>
|
||||
prev.includes(studentRkId) ? prev.filter((id) => id !== studentRkId) : [...prev, studentRkId]
|
||||
);
|
||||
};
|
||||
|
||||
const addSelectedStudents = async () => {
|
||||
if (selectedPickerRkIds.length === 0) return;
|
||||
try {
|
||||
await apiExam.addStudents(examId, selectedPickerRkIds);
|
||||
setSelectedPickerRkIds([]);
|
||||
setStudentPickerOpen(false);
|
||||
await load();
|
||||
await fetchOnline();
|
||||
} catch (e: any) {
|
||||
alert(e?.message || 'Thêm sinh viên thất bại');
|
||||
}
|
||||
};
|
||||
|
||||
const toggleSelectAllHits = () => {
|
||||
const enrolledIds = new Set(students.map((s) => s.studentRkId));
|
||||
const unaddedHits = searchHits.filter((h) => !enrolledIds.has(h.studentRkId));
|
||||
const allUnaddedIds = unaddedHits.map((h) => h.studentRkId);
|
||||
if (allUnaddedIds.length === 0) return;
|
||||
|
||||
const areAllSelected = allUnaddedIds.every((id) => selectedPickerRkIds.includes(id));
|
||||
if (areAllSelected) {
|
||||
setSelectedPickerRkIds((prev) => prev.filter((id) => !allUnaddedIds.includes(id)));
|
||||
} else {
|
||||
setSelectedPickerRkIds((prev) => Array.from(new Set([...prev, ...allUnaddedIds])));
|
||||
}
|
||||
};
|
||||
|
||||
const createPackage = async () => {
|
||||
if (!packagePdf) {
|
||||
setErr('Chọn file PDF cho gói đề');
|
||||
@@ -820,7 +854,7 @@ export const ExamRoomWorkspace: React.FC<Props> = ({ examId, onBack }) => {
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={() => { setStudentPickerOpen(true); setSearchQ(''); setSearchHits([]); }}
|
||||
onClick={() => { setStudentPickerOpen(true); setSearchQ(''); setSearchHits([]); setSelectedPickerRkIds([]); }}
|
||||
>
|
||||
+ Thêm sinh viên
|
||||
</button>
|
||||
@@ -846,7 +880,7 @@ export const ExamRoomWorkspace: React.FC<Props> = ({ examId, onBack }) => {
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary"
|
||||
onClick={() => { setStudentPickerOpen(true); setSearchQ(''); setSearchHits([]); }}
|
||||
onClick={() => { setStudentPickerOpen(true); setSearchQ(''); setSearchHits([]); setSelectedPickerRkIds([]); }}
|
||||
>
|
||||
+ Thêm sinh viên
|
||||
</button>
|
||||
@@ -1168,15 +1202,31 @@ export const ExamRoomWorkspace: React.FC<Props> = ({ examId, onBack }) => {
|
||||
</button>
|
||||
</div>
|
||||
<div className="exam-student-modal-body">
|
||||
<div className="exam-student-modal-search search-input-wrap">
|
||||
<div className="exam-student-modal-search search-input-wrap" style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
|
||||
<input
|
||||
type="search"
|
||||
className="search-input"
|
||||
placeholder="Tìm sinh viên..."
|
||||
value={searchQ}
|
||||
onChange={(e) => setSearchQ(e.target.value)}
|
||||
onChange={(e) => {
|
||||
setSearchQ(e.target.value);
|
||||
setSelectedPickerRkIds([]);
|
||||
}}
|
||||
autoFocus
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
{searchQ.trim() !== '' && searchHits.some((h) => !enrolledIds.has(h.studentRkId)) && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-sm"
|
||||
onClick={toggleSelectAllHits}
|
||||
style={{ whiteSpace: 'nowrap' }}
|
||||
>
|
||||
{searchHits.filter((h) => !enrolledIds.has(h.studentRkId)).every((h) => selectedPickerRkIds.includes(h.studentRkId))
|
||||
? 'Bỏ chọn tất cả'
|
||||
: 'Chọn tất cả'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="exam-student-hits">
|
||||
{searchQ.trim() === '' ? (
|
||||
@@ -1192,6 +1242,7 @@ export const ExamRoomWorkspace: React.FC<Props> = ({ examId, onBack }) => {
|
||||
) : (
|
||||
searchHits.map((h) => {
|
||||
const added = enrolledIds.has(h.studentRkId);
|
||||
const isSelected = selectedPickerRkIds.includes(h.studentRkId);
|
||||
const initials = h.fullName
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
@@ -1199,7 +1250,27 @@ export const ExamRoomWorkspace: React.FC<Props> = ({ examId, onBack }) => {
|
||||
.map((w) => w[0]?.toUpperCase() || '')
|
||||
.join('');
|
||||
return (
|
||||
<div key={h.studentRkId} className="exam-student-hit">
|
||||
<div
|
||||
key={h.studentRkId}
|
||||
className={`exam-student-hit ${added ? 'exam-student-hit--added' : ''} ${isSelected ? 'exam-student-hit--selected' : ''}`}
|
||||
style={{ cursor: added ? 'default' : 'pointer' }}
|
||||
onClick={() => {
|
||||
if (!added) toggleHitSelection(h.studentRkId);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{ display: 'flex', alignItems: 'center' }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={added || isSelected}
|
||||
disabled={added}
|
||||
onChange={() => {
|
||||
if (!added) toggleHitSelection(h.studentRkId);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="exam-student-hit-avatar" aria-hidden="true">{initials || '?'}</div>
|
||||
<div className="exam-student-hit-info">
|
||||
<strong>{h.fullName}</strong>
|
||||
@@ -1213,7 +1284,10 @@ export const ExamRoomWorkspace: React.FC<Props> = ({ examId, onBack }) => {
|
||||
type="button"
|
||||
className={`btn btn-sm ${added ? 'btn-secondary' : 'btn-primary'}`}
|
||||
disabled={added}
|
||||
onClick={() => addStudent(h.studentRkId)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
addStudent(h.studentRkId);
|
||||
}}
|
||||
>
|
||||
{added ? '✓ Đã thêm' : '+ Thêm'}
|
||||
</button>
|
||||
@@ -1223,6 +1297,31 @@ export const ExamRoomWorkspace: React.FC<Props> = ({ examId, onBack }) => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-footer exam-student-modal-footer" style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
gap: '0.75rem',
|
||||
padding: '0.85rem 1.25rem',
|
||||
borderTop: '1px solid var(--border-color, #dee2e6)',
|
||||
background: 'var(--bg-subtle, #f8f9fa)',
|
||||
borderRadius: '0 0 var(--radius-lg) var(--radius-lg)'
|
||||
}}>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary"
|
||||
onClick={() => setStudentPickerOpen(false)}
|
||||
>
|
||||
Đóng
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary"
|
||||
disabled={selectedPickerRkIds.length === 0}
|
||||
onClick={addSelectedStudents}
|
||||
>
|
||||
Thêm {selectedPickerRkIds.length > 0 ? `(${selectedPickerRkIds.length})` : ''} sinh viên đã chọn
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -4367,7 +4367,7 @@ input:checked + .slider:before {
|
||||
|
||||
.exam-student-hit {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
grid-template-columns: auto auto 1fr auto;
|
||||
align-items: center;
|
||||
gap: 0.85rem;
|
||||
padding: 0.85rem 1rem;
|
||||
@@ -4377,6 +4377,15 @@ input:checked + .slider:before {
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.exam-student-hit--added {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.exam-student-hit--selected {
|
||||
border-color: var(--accent);
|
||||
background-color: var(--accent-light, #fff5f5);
|
||||
}
|
||||
|
||||
.exam-student-hit:hover {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 2px 10px rgba(187, 33, 38, 0.08);
|
||||
|
||||
Reference in New Issue
Block a user