Spaces:
Sleeping
Sleeping
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // src/lib/api/studentsapi.ts | |
| // API layer for My Students page β matches actual backend endpoints | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| import { getApiBaseUrl } from './config'; | |
| const BASE_URL = getApiBaseUrl(); | |
| const TOKEN_KEY = 'authToken'; | |
| // βββ Shared helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function getHeaders(): HeadersInit { | |
| const token = | |
| localStorage.getItem(TOKEN_KEY) ?? | |
| localStorage.getItem('token') ?? | |
| ''; | |
| return { | |
| 'Content-Type': 'application/json', | |
| ...(token ? { Authorization: `Bearer ${token}` } : {}), | |
| }; | |
| } | |
| export class StudentsApiError extends Error { | |
| readonly status: number; | |
| constructor(status: number, message: string) { | |
| super(message); | |
| this.status = status; | |
| this.name = 'StudentsApiError'; | |
| } | |
| } | |
| async function request<T>(path: string, options?: RequestInit): Promise<T> { | |
| const res = await fetch(`${BASE_URL}${path}`, { | |
| ...options, | |
| headers: { ...getHeaders(), ...options?.headers }, | |
| }); | |
| // 204 No Content β return empty array gracefully | |
| if (res.status === 204) { | |
| return [] as unknown as T; | |
| } | |
| if (!res.ok) { | |
| const message = await res.text().catch(() => res.statusText); | |
| throw new StudentsApiError( | |
| res.status, | |
| `[${res.status}] ${path} β ${message}`, | |
| ); | |
| } | |
| const text = await res.text(); | |
| if (!text) return [] as unknown as T; | |
| try { | |
| return JSON.parse(text) as T; | |
| } catch { | |
| return text as unknown as T; | |
| } | |
| } | |
| // βββ Response Types βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| export interface Session { | |
| id: string; | |
| date: string; | |
| duration: number; | |
| status: 'completed' | 'cancelled' | 'upcoming'; | |
| rating?: number; | |
| feedback?: string; | |
| price: number; | |
| topic: string; | |
| } | |
| export interface Student { | |
| id: string; | |
| name: string; | |
| email: string; | |
| phone?: string; | |
| location?: string; | |
| joinedDate: string; | |
| totalSessions: number; | |
| completedSessions: number; | |
| cancelledSessions: number; | |
| totalMinutes: number; | |
| totalSpent: number; | |
| averageRating: number; | |
| lastSession: string; | |
| currentStreak: number; | |
| progress: number; | |
| level: string; | |
| goals: string[]; | |
| sessionHistory?: Session[]; | |
| } | |
| export interface StudentsStats { | |
| totalStudents: number; | |
| activeLast7Days: number; | |
| totalSessions: number; | |
| averageRating: number; | |
| } | |
| // βββ Raw-to-typed normalizer ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // Maps any backend field naming to our Student shape safely | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| function normalizeStudent(raw: any): Student { | |
| return { | |
| id: String( | |
| raw.id ?? raw.studentId ?? raw.userId ?? '', | |
| ), | |
| name: | |
| raw.name ?? | |
| raw.fullName ?? | |
| raw.studentName ?? | |
| '', | |
| email: raw.email ?? raw.emailAddress ?? '', | |
| phone: raw.phone ?? raw.phoneNumber ?? undefined, | |
| location: raw.location ?? raw.city ?? undefined, | |
| joinedDate: | |
| raw.joinedDate ?? raw.createdAt ?? new Date().toISOString(), | |
| totalSessions: raw.totalSessions ?? raw.sessionsCount ?? 0, | |
| completedSessions: raw.completedSessions ?? raw.completed ?? 0, | |
| cancelledSessions: raw.cancelledSessions ?? raw.cancelled ?? 0, | |
| totalMinutes: raw.totalMinutes ?? raw.totalTime ?? 0, | |
| totalSpent: | |
| raw.totalSpent ?? raw.totalPaid ?? raw.totalEarnings ?? 0, | |
| averageRating: raw.averageRating ?? raw.rating ?? 0, | |
| lastSession: | |
| raw.lastSession ?? | |
| raw.lastSessionDate ?? | |
| new Date().toISOString(), | |
| currentStreak: raw.currentStreak ?? raw.streak ?? 0, | |
| progress: raw.progress ?? raw.progressPercent ?? 0, | |
| level: raw.level ?? raw.studentLevel ?? 'Beginner', | |
| goals: Array.isArray(raw.goals) ? raw.goals : [], | |
| sessionHistory: Array.isArray(raw.sessionHistory) | |
| ? raw.sessionHistory | |
| : undefined, | |
| }; | |
| } | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| async function requestStudents(path: string): Promise<Student[]> { | |
| const raw = await request<unknown>(path); | |
| if (!Array.isArray(raw)) return []; | |
| return raw.map(normalizeStudent); | |
| } | |
| // βββ API Functions β exact endpoints from backend docs ββββββββββββββββββββββββ | |
| /** | |
| * GET /api/sheikh/my-students | |
| * All students the sheikh has had sessions with | |
| */ | |
| export function getAllStudents(): Promise<Student[]> { | |
| return requestStudents('/api/sheikh/my-students'); | |
| } | |
| /** | |
| * GET /api/sheikh/my-students/active-last-7-days | |
| * Students active in the past 7 days | |
| */ | |
| export function getActiveStudents(): Promise<Student[]> { | |
| return requestStudents('/api/sheikh/my-students/active-last-7-days'); | |
| } | |
| /** | |
| * GET /api/sheikh/my-students/top-students | |
| * Top performing students | |
| */ | |
| export function getTopStudents(): Promise<Student[]> { | |
| return requestStudents('/api/sheikh/my-students/top-students'); | |
| } | |
| /** | |
| * GET /api/sheikh/my-students/search?keyword=query | |
| * Search students by keyword | |
| */ | |
| export function searchStudents(keyword: string): Promise<Student[]> { | |
| return requestStudents( | |
| `/api/sheikh/my-students/search?keyword=${encodeURIComponent(keyword)}`, | |
| ); | |
| } | |
| /** | |
| * GET /api/sheikh/my-students/View-Student?id=id | |
| * Detailed info for a specific student | |
| */ | |
| export async function getStudentDetails(id: number): Promise<Student> { | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| const raw = await request<any>( | |
| `/api/sheikh/my-students/View-Student?id=${id}`, | |
| ); | |
| return normalizeStudent(raw); | |
| } | |
| /** | |
| * GET /api/sheikh/my-students/stats | |
| * Aggregate statistics for all the sheikh's students | |
| */ | |
| export async function getStudentsStats(): Promise<StudentsStats> { | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| const raw = await request<any>('/api/sheikh/my-students/stats'); | |
| return { | |
| totalStudents: | |
| raw.totalStudents ?? raw.total ?? 0, | |
| activeLast7Days: | |
| raw.activeLast7Days ?? raw.activeStudents ?? raw.active ?? 0, | |
| totalSessions: | |
| raw.totalSessions ?? raw.sessions ?? 0, | |
| averageRating: | |
| raw.averageRating ?? raw.avgRating ?? raw.rating ?? 0, | |
| }; | |
| } |