This commit is contained in:
@@ -17,6 +17,9 @@ import type {
|
||||
ScheduleConflictItem,
|
||||
} from '../api';
|
||||
import { LiveProctorModal } from './LiveProctorModal';
|
||||
import { AppPoolModal } from './AppPoolModal';
|
||||
import { AppTemplatePickerModal } from './AppTemplatePickerModal';
|
||||
import { mergeKeywordCSV } from '../utils/appKeywords';
|
||||
import { openClass } from './NavHistoryBar';
|
||||
import { ScheduleEditor } from './ScheduleEditor';
|
||||
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 [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 () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
@@ -451,11 +471,30 @@ export const LearningTab: React.FC = () => {
|
||||
backgroundColor: '#fff',
|
||||
outline: 'none',
|
||||
transition: 'var(--transition)',
|
||||
resize: 'vertical'
|
||||
resize: 'vertical',
|
||||
boxSizing: 'border-box'
|
||||
}}
|
||||
onFocus={(e) => e.target.style.borderColor = 'var(--accent)'}
|
||||
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>
|
||||
<button className="btn btn-primary" onClick={handleSaveApps} style={{ width: '100%', marginTop: '15px' }}>
|
||||
Lưu whitelist app
|
||||
@@ -674,6 +713,19 @@ export const LearningTab: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<AppPoolModal
|
||||
open={appPoolOpen}
|
||||
onClose={() => setAppPoolOpen(false)}
|
||||
onSelect={addAppKeyword}
|
||||
allowedApps={allowedApps}
|
||||
/>
|
||||
<AppTemplatePickerModal
|
||||
open={templatePickerOpen}
|
||||
onClose={() => setTemplatePickerOpen(false)}
|
||||
onSelect={applyTemplate}
|
||||
allowedApps={allowedApps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user