// ───────────────────────────────────────────────────────────────────────────── // src/lib/api/sheikhApi.ts // ───────────────────────────────────────────────────────────────────────────── import { getApiBaseUrl } from './config'; const BASE_URL = getApiBaseUrl(); const TOKEN_KEY = 'authToken'; function getHeaders(): HeadersInit { const token = localStorage.getItem(TOKEN_KEY) ?? ''; return { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}), }; } export class SheikhApiError extends Error { public readonly status: number; public readonly path: string; constructor(status: number, message: string, path: string) { super(`[${status}] ${path} → ${message}`); this.status = status; this.path = path; this.name = 'SheikhApiError'; } } async function request(path: string, options?: RequestInit): Promise { const res = await fetch(`${BASE_URL}${path}`, { ...options, headers: { ...getHeaders(), ...options?.headers }, }); if (res.status === 204) return null; if (!res.ok) { const message = await res.text().catch(() => res.statusText); throw new SheikhApiError(res.status, message, path); } const text = await res.text(); if (!text.trim()) return null; try { return JSON.parse(text) as T; } catch { return text as unknown as T; } } // ─── Response Types — matching ACTUAL API response fields ───────────────────── export interface SheikhStatsDto { hourlyRate: number; ratingAvg: number; totalEarnings: number; totalEarningsThisMonth: number; numberOfStudents: number; numberOfActiveStudents: number; numberOfCompletedSessionsThisMonth: number | null; numberOfSessionsThisMonth: number | null; numberOfSessionsScheduled?: number; numberOfCompletedSessions?: number; } /** * Matches actual API response: * { * sessionId, studentName, price, duration, * scheduledStart: "2026-03-13T05:01:12.500697", * sessionDescription, meetingLink, ... * } * Note: scheduledEnd is NOT in the response — duration field is used instead. */ export interface UpcomingClassDto { sessionId: number; studentId?: number; sheikhId?: number; studentName: string; sheikhName?: string; price: number; duration: number; // minutes — use this directly, don't calculate scheduledStartDate?: string; scheduledStartTime?: string; scheduledStart: string; // ISO: "2026-03-13T05:01:12.500697" actualStart?: string | null; actualEnd?: string | null; sessionNotes?: string | null; sessionDescription?: string | null; sessionStatus?: string; paymentStatus?: string; meetingLink?: string; createdAt?: string; } /** * Matches actual API response: * { * comment, date, rate, studentName * } * Note: field names are `date` and `rate` — NOT `createdAt` and `rating` */ export interface ReviewDto { studentName: string; comment: string; date: string; // "2026-03-06" rate: number; // 1-5 ← NOT "rating" // Legacy aliases (kept for safety if backend changes) reviewId?: number; createdAt?: string; rating?: number; } export interface UpdatePriceBody { newPrice: number; } export interface AvailabilitySlotDto { id: number; dayOfWeek: string; startTime: string; // "HH:mm:ss" endTime: string; // "HH:mm:ss" } // ─── API Functions ──────────────────────────────────────────────────────────── export async function fetchSheikhStats(): Promise { return request('/api/sheikh/sheikh-dashboard-view/stats'); } export async function fetchUpcomingClasses(): Promise { const raw = await request('/api/sheikh/sheikh-dashboard-view/upcoming-classes'); if (raw === null) return []; if (Array.isArray(raw)) return raw as UpcomingClassDto[]; // Single object returned by backend return [raw as UpcomingClassDto]; } export async function fetchTwoReviews(): Promise { const raw = await request('/api/sheikh/sheikh-dashboard-view/Two-reviews'); if (raw === null) return []; if (!Array.isArray(raw)) return []; return raw; } export async function fetchAllReviews(): Promise { const raw = await request('/api/sheikh/sheikh-dashboard-view/reviews'); if (raw === null) return []; if (!Array.isArray(raw)) return []; return raw; } export async function updateSheikhPrice(newPrice: number): Promise { await request('/api/sheikh/sheikh-dashboard-view/update-price', { method: 'PUT', body: JSON.stringify({ newPrice } satisfies UpdatePriceBody), }); } export async function fetchSheikhAvailability(): Promise { const raw = await request('/api/sheikh/availability'); if (!Array.isArray(raw)) return []; return raw; } export async function updateSheikhAvailability( slots: Array<{ dayOfWeek: string; startTime: string; endTime: string }> ): Promise { await request('/api/sheikh/availability', { method: 'PUT', body: JSON.stringify({ slots }), }); } // ─── Dashboard data loader ──────────────────────────────────────────────────── export interface SheikhDashboardData { stats: SheikhStatsDto; upcomingClasses: UpcomingClassDto[]; recentReviews: ReviewDto[]; } export async function fetchSheikhDashboardData(): Promise<{ data: Partial; errors: Record; }> { const [statsResult, upcomingResult, reviewsResult] = await Promise.allSettled([ fetchSheikhStats(), fetchUpcomingClasses(), fetchTwoReviews(), ]); const data: Partial = {}; const errors: Record = {}; if (statsResult.status === 'fulfilled') { if (statsResult.value !== null) { data.stats = statsResult.value; } else { errors.stats = 'No stats available (204)'; } } else { errors.stats = statsResult.reason?.message ?? 'Unknown error'; console.error('[sheikhApi] stats failed:', statsResult.reason); } if (upcomingResult.status === 'fulfilled') { data.upcomingClasses = upcomingResult.value; } else { errors.upcomingClasses = upcomingResult.reason?.message ?? 'Unknown error'; data.upcomingClasses = []; console.error('[sheikhApi] upcomingClasses failed:', upcomingResult.reason); } if (reviewsResult.status === 'fulfilled') { data.recentReviews = reviewsResult.value; } else { errors.recentReviews = reviewsResult.reason?.message ?? 'Unknown error'; data.recentReviews = []; console.error('[sheikhApi] recentReviews failed:', reviewsResult.reason); } return { data, errors }; } const sheikhApi = { fetchSheikhStats, fetchUpcomingClasses, fetchTwoReviews, fetchAllReviews, updateSheikhPrice, fetchSheikhDashboardData, fetchSheikhAvailability, updateSheikhAvailability, }; export default sheikhApi;