// ───────────────────────────────────────────────────────────────────────────── // 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(path: string, options?: RequestInit): Promise { 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 { const raw = await request(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 { 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 { return requestStudents('/api/sheikh/my-students/active-last-7-days'); } /** * GET /api/sheikh/my-students/top-students * Top performing students */ export function getTopStudents(): Promise { 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 { 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 { // eslint-disable-next-line @typescript-eslint/no-explicit-any const raw = await request( `/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 { // eslint-disable-next-line @typescript-eslint/no-explicit-any const raw = await request('/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, }; }