This commit is contained in:
2026-06-30 09:31:33 +07:00
parent c736326162
commit bbf8336664
77 changed files with 12601 additions and 556 deletions

View File

@@ -28,6 +28,7 @@ export interface StudentItem {
location?: string;
systemId?: number;
systemName?: string;
avatar?: string;
}
export interface StatsResponse {
@@ -54,6 +55,55 @@ export interface SyncStatus {
pageSize?: number;
}
export interface ClassScheduleItem {
id?: number;
classRkId?: number;
dayOfWeek: number;
period: number;
startTime: string;
endTime: string;
courseId: number;
courseName: string;
isActive: boolean;
}
export interface ClassCourseItem {
id: number;
name: string;
courseCode?: string;
}
export interface AttendanceRow {
studentRkId: number;
studentCode: string;
fullName: string;
email: string;
status: number;
statusLabel: string;
onlineMinutes: number;
statusEditedByTeacher: boolean;
}
export const ATTENDANCE_STATUS_OPTIONS = [
{ value: 0, label: 'Nghỉ không phép' },
{ value: 1, label: 'Nghỉ có phép' },
{ value: 2, label: 'Nghỉ nửa buổi' },
{ value: 3, label: 'Đi học muộn' },
{ value: 4, label: 'Đi học đầy đủ' },
];
export interface StudentSessionLogItem {
id: number;
studentRkId: number;
studentCode: string;
fullName: string;
email: string;
onlineSeconds: number;
offlineSeconds: number;
wifiSsids: string;
lastActiveAt?: string;
}
export const api = {
getStats: async (): Promise<StatsResponse> => {
const res = await fetch(`${API_BASE}/stats`);
@@ -87,7 +137,10 @@ export const api = {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ isStudying }),
});
if (!res.ok) throw new Error('Failed to update class studying status');
if (!res.ok) {
const errData = await res.json().catch(() => ({}));
throw new Error(errData.error || 'Failed to update class studying status');
}
return res.json();
},
@@ -143,3 +196,199 @@ export const api = {
return res.json();
},
};
// Export individual learning functions to simplify imports in components
export const apiFetchActiveSchedules = async (): Promise<{ data: any[] }> => {
const res = await fetch(`${API_BASE}/classes/schedules`);
if (!res.ok) throw new Error('Failed to fetch active schedules');
return res.json();
};
export const apiFetchClassSchedule = async (rkId: number): Promise<{ data: ClassScheduleItem[] }> => {
const res = await fetch(`${API_BASE}/classes/${rkId}/schedule`);
if (!res.ok) throw new Error('Failed to fetch class schedule');
return res.json();
};
export const apiSaveClassSchedule = async (rkId: number, schedules: ClassScheduleItem[]): Promise<any> => {
const res = await fetch(`${API_BASE}/classes/${rkId}/schedule`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ schedules }),
});
if (!res.ok) throw new Error('Failed to save class schedule');
return res.json();
};
export const apiDeleteClassSchedule = async (rkId: number): Promise<any> => {
const res = await fetch(`${API_BASE}/classes/${rkId}/schedule`, { method: 'DELETE' });
if (!res.ok) throw new Error('Failed to delete class schedule');
return res.json();
};
export const apiFetchAllowedApps = async (rkId: number): Promise<{ classRkId: number; keywords: string }> => {
const res = await fetch(`${API_BASE}/classes/${rkId}/allowed-apps`);
if (!res.ok) throw new Error('Failed to fetch allowed apps keywords');
return res.json();
};
export const apiSaveAllowedApps = async (rkId: number, keywords: string): Promise<any> => {
const res = await fetch(`${API_BASE}/classes/${rkId}/allowed-apps`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ keywords }),
});
if (!res.ok) throw new Error('Failed to save allowed apps keywords');
return res.json();
};
export interface AppPoolItem {
id: number;
processName: string;
windowTitle: string;
keyword: string;
hitCount: number;
lastSeenAt: string;
lastStudentRkId?: number;
lastClassRkId?: number;
}
export const apiFetchAppPool = async (q = '', limit = 50): Promise<{ data: AppPoolItem[]; q?: string; limit?: number }> => {
const params = new URLSearchParams();
if (q.trim()) params.set('q', q.trim());
params.set('limit', String(limit));
const query = params.toString() ? `?${params.toString()}` : '';
const res = await fetch(`${API_BASE}/app-pool${query}`);
if (!res.ok) throw new Error('Failed to fetch app pool');
return res.json();
};
export interface WifiPoolItem {
id: number;
ssid: string;
bssid: string;
hitCount: number;
lastSeenAt: string;
lastStudentRkId?: number;
}
export interface AcceptedWifiItem {
id: number;
ssid: string;
bssid: string;
createdAt: string;
}
export type WifiAcceptItem = { ssid: string; bssid: string };
export const apiFetchWifiPool = async (q = '', limit = 50): Promise<{ data: WifiPoolItem[] }> => {
const params = new URLSearchParams();
if (q.trim()) params.set('q', q.trim());
params.set('limit', String(limit));
const query = params.toString() ? `?${params.toString()}` : '';
const res = await fetch(`${API_BASE}/wifi-pool${query}`);
if (!res.ok) throw new Error('Failed to fetch wifi pool');
return res.json();
};
export const apiFetchAcceptedWifis = async (): Promise<{ data: AcceptedWifiItem[] }> => {
const res = await fetch(`${API_BASE}/network/accepted-wifis`);
if (!res.ok) throw new Error('Failed to fetch accepted wifis');
return res.json();
};
export const apiSaveAcceptedWifis = async (items: WifiAcceptItem[]): Promise<any> => {
const res = await fetch(`${API_BASE}/network/accepted-wifis`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items }),
});
if (!res.ok) throw new Error('Failed to save accepted wifis');
return res.json();
};
export const apiFetchClassStudents = api.getClassStudents;
export const apiFetchClasses = api.getClasses;
export const apiFetchClassSessionLogs = async (
rkId: number,
date?: string,
period?: number
): Promise<{ data: StudentSessionLogItem[]; period?: number; date?: string }> => {
const params = new URLSearchParams();
if (date) params.set('date', date);
if (period) params.set('period', String(period));
const query = params.toString() ? `?${params.toString()}` : '';
const res = await fetch(`${API_BASE}/classes/${rkId}/session-logs${query}`);
if (!res.ok) throw new Error('Failed to fetch class session logs');
return res.json();
};
export const apiFetchOnlineStudents = async (rkId: number): Promise<{ onlineStudentIds: number[] }> => {
const res = await fetch(`${API_BASE}/classes/${rkId}/online-students`);
if (!res.ok) throw new Error('Failed to fetch online students list');
return res.json();
};
export const apiFetchClassCourses = async (rkId: number): Promise<{
data: ClassCourseItem[];
source?: string;
cached?: boolean;
warning?: string;
hint?: string;
}> => {
const res = await fetch(`${API_BASE}/classes/${rkId}/courses`);
const body = await res.json().catch(() => ({}));
if (!res.ok) {
throw new Error(body.error || body.hint || 'Không tải được danh sách môn học từ QLĐT');
}
return body;
};
export const apiApplyScheduleTemplate = async (rkId: number): Promise<{ created: number; skipped: number }> => {
const res = await fetch(`${API_BASE}/classes/${rkId}/schedule/apply-template`, { method: 'POST' });
if (!res.ok) throw new Error('Failed to apply schedule template');
return res.json();
};
export const apiFetchAttendanceShifts = async (rkId: number, date?: string) => {
const q = date ? `?date=${date}` : '';
const res = await fetch(`${API_BASE}/classes/${rkId}/attendance/shifts${q}`);
if (!res.ok) throw new Error('Failed to fetch attendance shifts');
return res.json();
};
export const apiFetchAttendance = async (rkId: number, date: string, period: number) => {
const res = await fetch(`${API_BASE}/classes/${rkId}/attendance?date=${date}&period=${period}`);
if (!res.ok) throw new Error('Failed to fetch attendance');
return res.json();
};
export const apiUpdateAttendanceStatus = async (
rkId: number,
payload: { date: string; period: number; studentRkId: number; status: number }
) => {
const res = await fetch(`${API_BASE}/classes/${rkId}/attendance/status`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(err.error || 'Failed to update attendance status');
}
return res.json();
};
export const apiPushAttendanceQLDT = async (rkId: number, date: string, period: number) => {
const res = await fetch(`${API_BASE}/classes/${rkId}/attendance/push-qldt`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ date, period }),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(err.error || 'Failed to push attendance to QLĐT');
}
return res.json();
};