365 lines
8.9 KiB
TypeScript
365 lines
8.9 KiB
TypeScript
|
|
import { API_ENDPOINTS } from "@/lib/api_urls";
|
||
|
|
import { getStoredTokens } from "./auth";
|
||
|
|
import type {
|
||
|
|
CreateAppointmentInput,
|
||
|
|
ScheduleAppointmentInput,
|
||
|
|
RejectAppointmentInput,
|
||
|
|
UpdateAvailabilityInput,
|
||
|
|
} from "@/lib/schema/appointments";
|
||
|
|
import type {
|
||
|
|
Appointment,
|
||
|
|
AppointmentResponse,
|
||
|
|
AppointmentsListResponse,
|
||
|
|
AvailableDatesResponse,
|
||
|
|
AdminAvailability,
|
||
|
|
AppointmentStats,
|
||
|
|
JitsiMeetingInfo,
|
||
|
|
ApiError,
|
||
|
|
} from "@/lib/models/appointments";
|
||
|
|
|
||
|
|
// Helper function to extract error message from API response
|
||
|
|
function extractErrorMessage(error: ApiError): string {
|
||
|
|
if (error.detail) {
|
||
|
|
if (Array.isArray(error.detail)) {
|
||
|
|
return error.detail.join(", ");
|
||
|
|
}
|
||
|
|
return String(error.detail);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (error.message) {
|
||
|
|
if (Array.isArray(error.message)) {
|
||
|
|
return error.message.join(", ");
|
||
|
|
}
|
||
|
|
return String(error.message);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (typeof error === "string") {
|
||
|
|
return error;
|
||
|
|
}
|
||
|
|
|
||
|
|
return "An error occurred while creating the appointment";
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create appointment
|
||
|
|
export async function createAppointment(
|
||
|
|
input: CreateAppointmentInput
|
||
|
|
): Promise<Appointment> {
|
||
|
|
const tokens = getStoredTokens();
|
||
|
|
|
||
|
|
if (!tokens.access) {
|
||
|
|
throw new Error("Authentication required. Please log in to book an appointment.");
|
||
|
|
}
|
||
|
|
|
||
|
|
const response = await fetch(API_ENDPOINTS.meetings.createAppointment, {
|
||
|
|
method: "POST",
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
Authorization: `Bearer ${tokens.access}`,
|
||
|
|
},
|
||
|
|
body: JSON.stringify(input),
|
||
|
|
});
|
||
|
|
|
||
|
|
const data: AppointmentResponse = await response.json();
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const errorMessage = extractErrorMessage(data as ApiError);
|
||
|
|
throw new Error(errorMessage);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Handle different response formats
|
||
|
|
if (data.appointment) {
|
||
|
|
return data.appointment;
|
||
|
|
}
|
||
|
|
if ((data as any).data) {
|
||
|
|
return (data as any).data;
|
||
|
|
}
|
||
|
|
|
||
|
|
// If appointment is returned directly
|
||
|
|
return data as unknown as Appointment;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get available dates
|
||
|
|
export async function getAvailableDates(): Promise<string[]> {
|
||
|
|
const response = await fetch(API_ENDPOINTS.meetings.availableDates, {
|
||
|
|
method: "GET",
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
const data: AvailableDatesResponse | string[] = await response.json();
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const errorMessage = extractErrorMessage(data as ApiError);
|
||
|
|
throw new Error(errorMessage);
|
||
|
|
}
|
||
|
|
|
||
|
|
// API returns array of dates in YYYY-MM-DD format
|
||
|
|
if (Array.isArray(data)) {
|
||
|
|
return data;
|
||
|
|
}
|
||
|
|
return (data as AvailableDatesResponse).dates || [];
|
||
|
|
}
|
||
|
|
|
||
|
|
// List appointments (Admin sees all, users see their own)
|
||
|
|
export async function listAppointments(email?: string): Promise<Appointment[]> {
|
||
|
|
const tokens = getStoredTokens();
|
||
|
|
|
||
|
|
if (!tokens.access) {
|
||
|
|
throw new Error("Authentication required.");
|
||
|
|
}
|
||
|
|
|
||
|
|
const url = email
|
||
|
|
? `${API_ENDPOINTS.meetings.listAppointments}?email=${encodeURIComponent(email)}`
|
||
|
|
: API_ENDPOINTS.meetings.listAppointments;
|
||
|
|
|
||
|
|
const response = await fetch(url, {
|
||
|
|
method: "GET",
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
Authorization: `Bearer ${tokens.access}`,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
const data: AppointmentsListResponse = await response.json();
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const errorMessage = extractErrorMessage(data as ApiError);
|
||
|
|
throw new Error(errorMessage);
|
||
|
|
}
|
||
|
|
|
||
|
|
return data.appointments || [];
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get user appointments
|
||
|
|
export async function getUserAppointments(): Promise<Appointment[]> {
|
||
|
|
const tokens = getStoredTokens();
|
||
|
|
|
||
|
|
if (!tokens.access) {
|
||
|
|
throw new Error("Authentication required.");
|
||
|
|
}
|
||
|
|
|
||
|
|
const response = await fetch(API_ENDPOINTS.meetings.userAppointments, {
|
||
|
|
method: "GET",
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
Authorization: `Bearer ${tokens.access}`,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
const data: AppointmentsListResponse = await response.json();
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const errorMessage = extractErrorMessage(data as ApiError);
|
||
|
|
throw new Error(errorMessage);
|
||
|
|
}
|
||
|
|
|
||
|
|
return data.appointments || [];
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get appointment detail
|
||
|
|
export async function getAppointmentDetail(id: string): Promise<Appointment> {
|
||
|
|
const tokens = getStoredTokens();
|
||
|
|
|
||
|
|
if (!tokens.access) {
|
||
|
|
throw new Error("Authentication required.");
|
||
|
|
}
|
||
|
|
|
||
|
|
const response = await fetch(`${API_ENDPOINTS.meetings.listAppointments}${id}/`, {
|
||
|
|
method: "GET",
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
Authorization: `Bearer ${tokens.access}`,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
const data: AppointmentResponse = await response.json();
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const errorMessage = extractErrorMessage(data as ApiError);
|
||
|
|
throw new Error(errorMessage);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (data.appointment) {
|
||
|
|
return data.appointment;
|
||
|
|
}
|
||
|
|
|
||
|
|
return data as unknown as Appointment;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Schedule appointment (Admin only)
|
||
|
|
export async function scheduleAppointment(
|
||
|
|
id: string,
|
||
|
|
input: ScheduleAppointmentInput
|
||
|
|
): Promise<Appointment> {
|
||
|
|
const tokens = getStoredTokens();
|
||
|
|
|
||
|
|
if (!tokens.access) {
|
||
|
|
throw new Error("Authentication required.");
|
||
|
|
}
|
||
|
|
|
||
|
|
const response = await fetch(`${API_ENDPOINTS.meetings.listAppointments}${id}/schedule/`, {
|
||
|
|
method: "POST",
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
Authorization: `Bearer ${tokens.access}`,
|
||
|
|
},
|
||
|
|
body: JSON.stringify(input),
|
||
|
|
});
|
||
|
|
|
||
|
|
const data: AppointmentResponse = await response.json();
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const errorMessage = extractErrorMessage(data as ApiError);
|
||
|
|
throw new Error(errorMessage);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (data.appointment) {
|
||
|
|
return data.appointment;
|
||
|
|
}
|
||
|
|
|
||
|
|
return data as unknown as Appointment;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Reject appointment (Admin only)
|
||
|
|
export async function rejectAppointment(
|
||
|
|
id: string,
|
||
|
|
input: RejectAppointmentInput
|
||
|
|
): Promise<Appointment> {
|
||
|
|
const tokens = getStoredTokens();
|
||
|
|
|
||
|
|
if (!tokens.access) {
|
||
|
|
throw new Error("Authentication required.");
|
||
|
|
}
|
||
|
|
|
||
|
|
const response = await fetch(`${API_ENDPOINTS.meetings.listAppointments}${id}/reject/`, {
|
||
|
|
method: "POST",
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
Authorization: `Bearer ${tokens.access}`,
|
||
|
|
},
|
||
|
|
body: JSON.stringify(input),
|
||
|
|
});
|
||
|
|
|
||
|
|
const data: AppointmentResponse = await response.json();
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const errorMessage = extractErrorMessage(data as ApiError);
|
||
|
|
throw new Error(errorMessage);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (data.appointment) {
|
||
|
|
return data.appointment;
|
||
|
|
}
|
||
|
|
|
||
|
|
return data as unknown as Appointment;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get admin availability
|
||
|
|
export async function getAdminAvailability(): Promise<AdminAvailability> {
|
||
|
|
const tokens = getStoredTokens();
|
||
|
|
|
||
|
|
if (!tokens.access) {
|
||
|
|
throw new Error("Authentication required.");
|
||
|
|
}
|
||
|
|
|
||
|
|
const response = await fetch(`${API_ENDPOINTS.meetings.base}admin/availability/`, {
|
||
|
|
method: "GET",
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
Authorization: `Bearer ${tokens.access}`,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
const data: AdminAvailability = await response.json();
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const errorMessage = extractErrorMessage(data as ApiError);
|
||
|
|
throw new Error(errorMessage);
|
||
|
|
}
|
||
|
|
|
||
|
|
return data;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update admin availability
|
||
|
|
export async function updateAdminAvailability(
|
||
|
|
input: UpdateAvailabilityInput
|
||
|
|
): Promise<AdminAvailability> {
|
||
|
|
const tokens = getStoredTokens();
|
||
|
|
|
||
|
|
if (!tokens.access) {
|
||
|
|
throw new Error("Authentication required.");
|
||
|
|
}
|
||
|
|
|
||
|
|
const response = await fetch(`${API_ENDPOINTS.meetings.base}admin/availability/`, {
|
||
|
|
method: "PUT",
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
Authorization: `Bearer ${tokens.access}`,
|
||
|
|
},
|
||
|
|
body: JSON.stringify(input),
|
||
|
|
});
|
||
|
|
|
||
|
|
const data: AdminAvailability = await response.json();
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const errorMessage = extractErrorMessage(data as ApiError);
|
||
|
|
throw new Error(errorMessage);
|
||
|
|
}
|
||
|
|
|
||
|
|
return data;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get appointment stats (Admin only)
|
||
|
|
export async function getAppointmentStats(): Promise<AppointmentStats> {
|
||
|
|
const tokens = getStoredTokens();
|
||
|
|
|
||
|
|
if (!tokens.access) {
|
||
|
|
throw new Error("Authentication required.");
|
||
|
|
}
|
||
|
|
|
||
|
|
const response = await fetch(`${API_ENDPOINTS.meetings.listAppointments}stats/`, {
|
||
|
|
method: "GET",
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
Authorization: `Bearer ${tokens.access}`,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
const data: AppointmentStats = await response.json();
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const errorMessage = extractErrorMessage(data as ApiError);
|
||
|
|
throw new Error(errorMessage);
|
||
|
|
}
|
||
|
|
|
||
|
|
return data;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get Jitsi meeting info
|
||
|
|
export async function getJitsiMeetingInfo(id: string): Promise<JitsiMeetingInfo> {
|
||
|
|
const tokens = getStoredTokens();
|
||
|
|
|
||
|
|
if (!tokens.access) {
|
||
|
|
throw new Error("Authentication required.");
|
||
|
|
}
|
||
|
|
|
||
|
|
const response = await fetch(`${API_ENDPOINTS.meetings.listAppointments}${id}/jitsi-meeting/`, {
|
||
|
|
method: "GET",
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
Authorization: `Bearer ${tokens.access}`,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
const data: JitsiMeetingInfo = await response.json();
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const errorMessage = extractErrorMessage(data as ApiError);
|
||
|
|
throw new Error(errorMessage);
|
||
|
|
}
|
||
|
|
|
||
|
|
return data;
|
||
|
|
}
|
||
|
|
|