Quran_Tech_Server / F_Pro /src /lib /api /studentsapi.ts
aboalaa147's picture
Initial deployment
eb6a2f9
Raw
History Blame Contribute Delete
6.88 kB
// ─────────────────────────────────────────────────────────────────────────────
// 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,
};
}