fix ui
All checks were successful
Deploy on Master Change / deploy (push) Successful in 46s

This commit is contained in:
2026-07-03 18:09:22 +07:00
parent 96391b0706
commit 661f8e6267
3 changed files with 133 additions and 22 deletions

View File

@@ -148,26 +148,48 @@ export const ExamsTab: React.FC = () => {
</div> </div>
{showCreate && ( {showCreate && (
<div className="system-form-card exams-create-card"> <div className="modal-overlay" onClick={() => setShowCreate(false)}>
<h2 className="system-form-title">Phòng thi mới</h2> <div className="modal-container" style={{ maxWidth: '500px' }} onClick={e => e.stopPropagation()}>
<div className="system-form-grid"> <div className="modal-header">
<label className="login-field system-form-full"> <h2 className="modal-title">Tạo phòng thi mới</h2>
<span>Tên phòng thi</span> <button className="modal-close-btn" onClick={() => setShowCreate(false)} aria-label="Đóng">&times;</button>
<input value={name} onChange={(e) => setName(e.target.value)} placeholder="VD: Thi cuối kỳ Java" /> </div>
</label> <div className="modal-body" style={{ padding: '1.5rem', display: 'flex', flexDirection: 'column', gap: '1.25rem' }}>
<label className="login-field"> <label className="login-field" style={{ display: 'flex', flexDirection: 'column', gap: '0.35rem' }}>
<span>Bắt đu</span> <span style={{ fontWeight: 600, fontSize: '0.85rem', color: 'var(--text-secondary)' }}>Tên phòng thi</span>
<input type="datetime-local" value={start} onChange={(e) => setStart(e.target.value)} /> <input
</label> className="search-input"
<label className="login-field"> style={{ width: '100%', boxSizing: 'border-box' }}
<span>Kết thúc</span> value={name}
<input type="datetime-local" value={end} onChange={(e) => setEnd(e.target.value)} /> onChange={(e) => setName(e.target.value)}
</label> placeholder="VD: Thi cuối kỳ Java"
</div> />
<div className="system-form-actions"> </label>
<div /> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
<div className="system-form-actions-right"> <label className="login-field" style={{ display: 'flex', flexDirection: 'column', gap: '0.35rem' }}>
<button type="button" className="btn btn-ghost" onClick={() => setShowCreate(false)}> <span style={{ fontWeight: 600, fontSize: '0.85rem', color: 'var(--text-secondary)' }}>Bắt đu</span>
<input
type="datetime-local"
className="search-input"
style={{ width: '100%', boxSizing: 'border-box', padding: '0.5rem 0.75rem' }}
value={start}
onChange={(e) => setStart(e.target.value)}
/>
</label>
<label className="login-field" style={{ display: 'flex', flexDirection: 'column', gap: '0.35rem' }}>
<span style={{ fontWeight: 600, fontSize: '0.85rem', color: 'var(--text-secondary)' }}>Kết thúc</span>
<input
type="datetime-local"
className="search-input"
style={{ width: '100%', boxSizing: 'border-box', padding: '0.5rem 0.75rem' }}
value={end}
onChange={(e) => setEnd(e.target.value)}
/>
</label>
</div>
</div>
<div className="modal-footer" style={{ gap: '0.65rem' }}>
<button type="button" className="btn btn-secondary" onClick={() => setShowCreate(false)}>
Hủy Hủy
</button> </button>
<button type="button" className="btn btn-primary" disabled={busy} onClick={create}> <button type="button" className="btn btn-primary" disabled={busy} onClick={create}>

View File

@@ -17,6 +17,9 @@ import type {
ScheduleConflictItem, ScheduleConflictItem,
} from '../api'; } from '../api';
import { LiveProctorModal } from './LiveProctorModal'; import { LiveProctorModal } from './LiveProctorModal';
import { AppPoolModal } from './AppPoolModal';
import { AppTemplatePickerModal } from './AppTemplatePickerModal';
import { mergeKeywordCSV } from '../utils/appKeywords';
import { openClass } from './NavHistoryBar'; import { openClass } from './NavHistoryBar';
import { ScheduleEditor } from './ScheduleEditor'; import { ScheduleEditor } from './ScheduleEditor';
import { LearningTimetable } from './LearningTimetable'; import { LearningTimetable } from './LearningTimetable';
@@ -53,6 +56,23 @@ export const LearningTab: React.FC = () => {
const [proctorStudent, setProctorStudent] = useState<{ id: number; name: string; code: string } | null>(null); const [proctorStudent, setProctorStudent] = useState<{ id: number; name: string; code: string } | null>(null);
const [conflictWarning, setConflictWarning] = useState<{ cls: ClassItem; conflicts: ScheduleConflictItem[] } | null>(null); const [conflictWarning, setConflictWarning] = useState<{ cls: ClassItem; conflicts: ScheduleConflictItem[] } | null>(null);
const [appPoolOpen, setAppPoolOpen] = useState(false);
const [templatePickerOpen, setTemplatePickerOpen] = useState(false);
const addAppKeyword = (kw: string) => {
const trimmed = kw.trim().toLowerCase();
if (!trimmed) return;
setAllowedApps(prev => {
const parts = prev.split(',').map(s => s.trim().toLowerCase()).filter(Boolean);
if (parts.includes(trimmed)) return prev;
return prev ? `${prev}, ${trimmed}` : trimmed;
});
};
const applyTemplate = (keywords: string) => {
setAllowedApps((prev) => mergeKeywordCSV(prev, keywords));
};
const loadActiveClasses = async () => { const loadActiveClasses = async () => {
setLoading(true); setLoading(true);
try { try {
@@ -451,11 +471,30 @@ export const LearningTab: React.FC = () => {
backgroundColor: '#fff', backgroundColor: '#fff',
outline: 'none', outline: 'none',
transition: 'var(--transition)', transition: 'var(--transition)',
resize: 'vertical' resize: 'vertical',
boxSizing: 'border-box'
}} }}
onFocus={(e) => e.target.style.borderColor = 'var(--accent)'} onFocus={(e) => e.target.style.borderColor = 'var(--accent)'}
onBlur={(e) => e.target.style.borderColor = 'var(--border-color)'} onBlur={(e) => e.target.style.borderColor = 'var(--border-color)'}
/> />
<div style={{ display: 'flex', gap: '8px', marginTop: '10px' }}>
<button
type="button"
className="btn btn-secondary btn-sm"
style={{ flex: 1 }}
onClick={() => setTemplatePickerOpen(true)}
>
Chọn khung
</button>
<button
type="button"
className="btn btn-secondary btn-sm"
style={{ flex: 1 }}
onClick={() => setAppPoolOpen(true)}
>
Mở kho
</button>
</div>
</div> </div>
<button className="btn btn-primary" onClick={handleSaveApps} style={{ width: '100%', marginTop: '15px' }}> <button className="btn btn-primary" onClick={handleSaveApps} style={{ width: '100%', marginTop: '15px' }}>
Lưu whitelist app Lưu whitelist app
@@ -674,6 +713,19 @@ export const LearningTab: React.FC = () => {
</div> </div>
</div> </div>
)} )}
<AppPoolModal
open={appPoolOpen}
onClose={() => setAppPoolOpen(false)}
onSelect={addAppKeyword}
allowedApps={allowedApps}
/>
<AppTemplatePickerModal
open={templatePickerOpen}
onClose={() => setTemplatePickerOpen(false)}
onSelect={applyTemplate}
allowedApps={allowedApps}
/>
</div> </div>
); );
}; };

View File

@@ -111,6 +111,40 @@ export const ScheduleEditor: React.FC<ScheduleEditorProps> = ({ classId, schedul
} }
}; };
const handleQuickCreateLocal = () => {
const nextLocal = [...local];
const standardTimes = [
{ period: 1, start: '07:00', end: '09:00' },
{ period: 2, start: '09:10', end: '11:10' },
{ period: 3, start: '12:10', end: '14:10' },
{ period: 4, start: '14:20', end: '16:20' },
];
let added = 0;
for (let day = 0; day <= 4; day++) {
for (const slot of standardTimes) {
const exists = nextLocal.some(s => s.dayOfWeek === day && s.period === slot.period);
if (!exists) {
nextLocal.push({
dayOfWeek: day,
period: slot.period,
startTime: slot.start,
endTime: slot.end,
courseId: 0,
courseName: '',
isActive: false, // status is tắt (false)
});
added++;
}
}
}
if (added === 0) {
alert('Tất cả các ca học từ T2 đến T6 đã tồn tại.');
return;
}
setLocal(nextLocal);
alert(`Đã tạo nhanh ${added} ca học nháp (trạng thái Tắt) cho Thứ 26. Vui lòng chọn môn học và bấm "Lưu lịch học".`);
};
const handleSave = async () => { const handleSave = async () => {
const payload = local.filter(s => s.startTime?.trim() && s.endTime?.trim()); const payload = local.filter(s => s.startTime?.trim() && s.endTime?.trim());
try { try {
@@ -127,7 +161,10 @@ export const ScheduleEditor: React.FC<ScheduleEditorProps> = ({ classId, schedul
return ( return (
<div className="schedule-editor"> <div className="schedule-editor">
<div className="schedule-editor-toolbar"> <div className="schedule-editor-toolbar" style={{ display: 'flex', gap: '8px', marginBottom: '10px' }}>
<button type="button" className="btn btn-secondary" onClick={handleQuickCreateLocal}>
Tạo nhanh 4 ca T2T6 (Tắt)
</button>
<button type="button" className="btn btn-secondary" onClick={handleApplyTemplate} disabled={applying}> <button type="button" className="btn btn-secondary" onClick={handleApplyTemplate} disabled={applying}>
{applying ? 'Đang áp dụng...' : 'Template 4 ca (T2T6)'} {applying ? 'Đang áp dụng...' : 'Template 4 ca (T2T6)'}
</button> </button>