dvc890's picture
Upload 66 files
019d28a verified
// ... existing imports
import { User, ClassInfo, SystemConfig, Subject, School, Schedule, GameSession, StudentReward, LuckyDrawConfig, Attendance, LeaveRequest, AchievementConfig, SchoolCalendarEntry, TeacherExchangeConfig, Wish, Feedback, AIChatMessage, Todo } from '../types';
// ... existing getBaseUrl ...
const getBaseUrl = () => {
let isProd = false;
try {
// @ts-ignore
if (typeof import.meta !== 'undefined' && import.meta.env && import.meta.env.PROD) {
isProd = true;
}
} catch (e) {}
if (isProd || (typeof window !== 'undefined' && window.location.port === '7860')) {
return '/api';
}
return 'http://localhost:7860/api';
};
const API_BASE_URL = getBaseUrl();
async function request(endpoint: string, options: RequestInit = {}) {
const headers: any = { 'Content-Type': 'application/json', ...options.headers };
if (typeof window !== 'undefined') {
const currentUser = JSON.parse(localStorage.getItem('user') || 'null');
const selectedSchoolId = localStorage.getItem('admin_view_school_id');
if (currentUser?.role === 'ADMIN' && selectedSchoolId) {
headers['x-school-id'] = selectedSchoolId;
} else if (currentUser?.schoolId) {
headers['x-school-id'] = currentUser.schoolId;
}
if (currentUser?.role) {
headers['x-user-role'] = currentUser.role;
headers['x-user-username'] = currentUser.username;
}
}
const res = await fetch(`${API_BASE_URL}${endpoint}`, { ...options, headers });
if (!res.ok) {
if (res.status === 401) throw new Error('AUTH_FAILED');
const errorData = await res.json().catch(() => ({}));
const errorMessage = errorData.error || errorData.message || `Server Error: ${res.status}`;
if (errorData.error === 'PENDING_APPROVAL') throw new Error('PENDING_APPROVAL');
if (errorData.error === 'BANNED') throw new Error('BANNED');
if (errorData.error === 'CONFLICT') throw new Error(errorData.message);
if (errorData.error === 'INVALID_PASSWORD') throw new Error('INVALID_PASSWORD');
throw new Error(errorMessage);
}
return res.json();
}
export const api = {
init: () => console.log('🔗 API:', API_BASE_URL),
auth: {
login: async (username: string, password: string): Promise<User> => {
const user = await request('/auth/login', { method: 'POST', body: JSON.stringify({ username, password }) });
if (typeof window !== 'undefined') {
localStorage.setItem('user', JSON.stringify(user));
localStorage.removeItem('admin_view_school_id');
}
return user;
},
refreshSession: async (): Promise<User | null> => {
try {
const user = await request('/auth/me');
if (typeof window !== 'undefined' && user) {
localStorage.setItem('user', JSON.stringify(user));
}
return user;
} catch (e) { return null; }
},
register: async (data: any): Promise<User> => {
return await request('/auth/register', { method: 'POST', body: JSON.stringify(data) });
},
updateProfile: async (data: any): Promise<any> => {
return await request('/auth/update-profile', { method: 'POST', body: JSON.stringify(data) });
},
logout: () => {
if (typeof window !== 'undefined') {
localStorage.removeItem('user');
localStorage.removeItem('admin_view_school_id');
}
},
getCurrentUser: (): User | null => {
if (typeof window !== 'undefined') {
try {
const stored = localStorage.getItem('user');
if (stored) return JSON.parse(stored);
} catch (e) {
localStorage.removeItem('user');
}
}
return null;
}
},
schools: {
getPublic: () => request('/public/schools'),
getAll: () => request('/schools'),
add: (data: School) => request('/schools', { method: 'POST', body: JSON.stringify(data) }),
update: (id: string, data: Partial<School>) => request(`/schools/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
delete: (id: string) => request(`/schools/${id}`, { method: 'DELETE' })
},
users: {
getAll: (options?: { global?: boolean; role?: string }) => {
const params = new URLSearchParams();
if (options?.global) params.append('global', 'true');
if (options?.role) params.append('role', options.role);
return request(`/users?${params.toString()}`);
},
update: (id: string, data: Partial<User>) => request(`/users/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
delete: (id: string) => request(`/users/${id}`, { method: 'DELETE' }),
applyClass: (data: { userId: string, type: 'CLAIM'|'RESIGN', targetClass?: string, action: 'APPLY'|'APPROVE'|'REJECT' }) =>
request('/users/class-application', { method: 'POST', body: JSON.stringify(data) }),
getTeachersForClass: (className: string) => request(`/classes/${encodeURIComponent(className)}/teachers`),
saveMenuOrder: (userId: string, order: string[]) => request(`/users/${userId}/menu-order`, { method: 'PUT', body: JSON.stringify({ menuOrder: order }) }), // NEW
},
students: {
getAll: () => request('/students'),
add: (data: any) => request('/students', { method: 'POST', body: JSON.stringify(data) }),
update: (id: string, data: any) => request(`/students/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
delete: (id: string | number) => request(`/students/${id}`, { method: 'DELETE' }),
promote: (data: { teacherFollows: boolean }) => request('/students/promote', { method: 'POST', body: JSON.stringify(data) }),
transfer: (data: { studentId: string, targetClass: string }) => request('/students/transfer', { method: 'POST', body: JSON.stringify(data) })
},
classes: {
getAll: () => request('/classes'),
add: (data: ClassInfo) => request('/classes', { method: 'POST', body: JSON.stringify(data) }),
delete: (id: string | number) => request(`/classes/${id}`, { method: 'DELETE' })
},
subjects: {
getAll: () => request('/subjects'),
add: (data: Subject) => request('/subjects', { method: 'POST', body: JSON.stringify(data) }),
update: (id: string | number, data: Partial<Subject>) => request(`/subjects/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
delete: (id: string | number) => request(`/subjects/${id}`, { method: 'DELETE' })
},
exams: {
getAll: () => request('/exams'),
save: (data: any) => request('/exams', { method: 'POST', body: JSON.stringify(data) })
},
courses: {
getAll: () => request('/courses'),
add: (data: any) => request('/courses', { method: 'POST', body: JSON.stringify(data) }),
update: (id: string | number, data: any) => request(`/courses/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
delete: (id: string | number) => request(`/courses/${id}`, { method: 'DELETE' })
},
scores: {
getAll: () => request('/scores'),
add: (data: any) => request('/scores', { method: 'POST', body: JSON.stringify(data) }),
update: (id: string | number, data: any) => request(`/scores/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
delete: (id: string | number) => request(`/scores/${id}`, { method: 'DELETE' })
},
schedules: {
get: (params: { className?: string; teacherName?: string; grade?: string }) => {
const qs = new URLSearchParams(params as any).toString();
return request(`/schedules?${qs}`);
},
save: (data: Schedule) => request('/schedules', { method: 'POST', body: JSON.stringify(data) }),
delete: (params: { className: string; dayOfWeek: number; period: number }) => {
const qs = new URLSearchParams(params as any).toString();
return request(`/schedules?${qs}`, { method: 'DELETE' });
}
},
attendance: {
checkIn: (data: { studentId: string, date: string, status?: string }) => request('/attendance/check-in', { method: 'POST', body: JSON.stringify(data) }),
get: (params: { className?: string, date?: string, studentId?: string }) => {
const qs = new URLSearchParams(params as any).toString();
return request(`/attendance?${qs}`);
},
batch: (data: { className: string, date: string, status?: string }) => request('/attendance/batch', { method: 'POST', body: JSON.stringify(data) }),
update: (data: { studentId: string, date: string, status: string }) => request('/attendance/update', { method: 'PUT', body: JSON.stringify(data) }),
applyLeave: (data: { studentId: string, studentName: string, className: string, reason: string, startDate: string, endDate: string }) => request('/leave', { method: 'POST', body: JSON.stringify(data) }),
},
calendar: {
get: (className: string) => request(`/attendance/calendar?className=${className}`),
add: (data: SchoolCalendarEntry) => request('/attendance/calendar', { method: 'POST', body: JSON.stringify(data) }),
delete: (id: string) => request(`/attendance/calendar/${id}`, { method: 'DELETE' })
},
stats: {
getSummary: () => request('/stats')
},
config: {
get: () => request('/config'),
getPublic: () => request('/public/config'),
save: (data: SystemConfig) => request('/config', { method: 'POST', body: JSON.stringify(data) })
},
notifications: {
getAll: (userId: string, role: string) => request(`/notifications?userId=${userId}&role=${role}`),
},
games: {
getMountainSession: (className: string) => request(`/games/mountain?className=${className}`),
saveMountainSession: (data: GameSession) => request('/games/mountain', { method: 'POST', body: JSON.stringify(data) }),
getLuckyConfig: (className?: string, ownerId?: string) => request(`/games/lucky-config?className=${className || ''}${ownerId ? `&ownerId=${ownerId}` : ''}`),
saveLuckyConfig: (data: LuckyDrawConfig) => request('/games/lucky-config', { method: 'POST', body: JSON.stringify(data) }),
drawLucky: (studentId: string) => request('/games/lucky-draw', { method: 'POST', body: JSON.stringify({ studentId }) }),
grantReward: (data: { studentId: string, count: number, rewardType: string, name?: string }) => request('/games/grant-reward', { method: 'POST', body: JSON.stringify(data) }),
getMonsterConfig: (className: string) => request(`/games/monster-config?className=${className}`),
saveMonsterConfig: (data: any) => request('/games/monster-config', { method: 'POST', body: JSON.stringify(data) }),
getZenConfig: (className: string) => request(`/games/zen-config?className=${className}`),
saveZenConfig: (data: any) => request('/games/zen-config', { method: 'POST', body: JSON.stringify(data) }),
},
achievements: {
getConfig: (className: string) => request(`/achievements/config?className=${className}`),
saveConfig: (data: AchievementConfig) => request('/achievements/config', { method: 'POST', body: JSON.stringify(data) }),
getStudentAchievements: (studentId: string, semester?: string) => request(`/achievements/student?studentId=${studentId}${semester ? `&semester=${semester}` : ''}`),
getClassHistory: (studentIds: string[]) => request(`/achievements/student?studentIds=${studentIds.join(',')}&semester=ALL`), // NEW
grant: (data: { studentId: string, achievementId: string, semester: string }) => request('/achievements/grant', { method: 'POST', body: JSON.stringify(data) }),
deleteRecord: (id: string) => request(`/achievements/record/${id}`, { method: 'DELETE' }), // NEW
exchange: (data: { studentId: string, ruleId: string, teacherId?: string }) => request('/achievements/exchange', { method: 'POST', body: JSON.stringify(data) }),
getMyRules: () => request('/achievements/teacher-rules'),
saveMyRules: (data: TeacherExchangeConfig) => request('/achievements/teacher-rules', { method: 'POST', body: JSON.stringify(data) }),
getRulesByTeachers: (teacherIds: string[]) => request(`/achievements/teacher-rules?teacherIds=${teacherIds.join(',')}`),
},
rewards: {
getMyRewards: (studentId: string, page = 1, limit = 20) => request(`/rewards?studentId=${studentId}&page=${page}&limit=${limit}&excludeType=CONSOLATION`),
getClassRewards: (page = 1, limit = 20, className?: string) => {
let qs = `scope=class&page=${page}&limit=${limit}&excludeType=CONSOLATION`;
if (className) qs += `&className=${className}`;
return request(`/rewards?${qs}`);
},
addReward: (data: Partial<StudentReward>) => request('/rewards', { method: 'POST', body: JSON.stringify(data) }),
update: (id: string, data: Partial<StudentReward>) => request(`/rewards/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
delete: (id: string) => request(`/rewards/${id}`, { method: 'DELETE' }),
redeem: (id: string) => request(`/rewards/${id}/redeem`, { method: 'POST' }),
},
batchDelete: (type: 'student' | 'score' | 'user', ids: string[]) => {
return request('/batch-delete', { method: 'POST', body: JSON.stringify({ type, ids }) });
},
wishes: {
getAll: (params: { teacherId?: string, studentId?: string, status?: string }) => {
const qs = new URLSearchParams(params as any).toString();
return request(`/wishes?${qs}`);
},
create: (data: Partial<Wish>) => request('/wishes', { method: 'POST', body: JSON.stringify(data) }),
fulfill: (id: string) => request(`/wishes/${id}/fulfill`, { method: 'POST' }),
randomFulfill: (teacherId: string) => request('/wishes/random-fulfill', { method: 'POST', body: JSON.stringify({ teacherId }) }),
},
feedback: {
getAll: (params: { targetId?: string, creatorId?: string, type?: string, status?: string }) => {
const qs = new URLSearchParams(params as any).toString();
return request(`/feedback?${qs}`);
},
create: (data: Partial<Feedback>) => request('/feedback', { method: 'POST', body: JSON.stringify(data) }),
update: (id: string, data: { status?: string, reply?: string }) => request(`/feedback/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
ignoreAll: (targetId: string) => request('/feedback/ignore-all', { method: 'POST', body: JSON.stringify({ targetId }) }),
},
ai: {
chat: (data: { text?: string, audio?: string, history?: { role: string, text?: string }[], enableThinking?: boolean, overrideSystemPrompt?: string, disableAudio?: boolean }) => request('/ai/chat', { method: 'POST', body: JSON.stringify(data) }),
evaluate: (data: { question: string, audio?: string, image?: string }) => request('/ai/evaluate', { method: 'POST', body: JSON.stringify(data) }),
resetPool: () => request('/ai/reset-pool', { method: 'POST' }),
getStats: () => request('/ai/stats'), // NEW Detailed Stats
},
todos: { // NEW
getAll: () => request('/todos'),
add: (content: string) => request('/todos', { method: 'POST', body: JSON.stringify({ content }) }),
update: (id: string, data: Partial<Todo>) => request(`/todos/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
delete: (id: string) => request(`/todos/${id}`, { method: 'DELETE' }),
}
};