221 lines
9.7 KiB
TypeScript
221 lines
9.7 KiB
TypeScript
// apiService.ts (V4)
|
|
import {
|
|
AuthResponse, UserProfile, Patient, Doctor, Appointment, NewAppointmentPayload,
|
|
AvailableSlot, Report, ReportInput, DoctorAvailability, DoctorException,
|
|
NetworkError, LoginResponse, SendMagicLinkResponse, LogoutResponse,
|
|
GetCurrentUserResponse, RequestPasswordResetResponse, CreateUserWithPasswordResponse,
|
|
HardDeleteUserResponse, RegisterPatientResponse, GetAvailableSlotsResponse,
|
|
CreateAppointmentResponse, CancelAppointmentResponse, CreateReportResponse,
|
|
UpdateReportResponse, ListResponse
|
|
} from './types';
|
|
|
|
// Ação Futura: Mover estas chaves para variáveis de ambiente.
|
|
const BASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL || 'https://yuanqfswhberkoevtmfr.supabase.co';
|
|
const API_KEY = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ';
|
|
|
|
/**
|
|
* Cliente de API base que retorna uma união discriminada para todos os cenários.
|
|
*/
|
|
async function apiClient<T>(endpoint: string, options: RequestInit = {}, isPublic: boolean = false): Promise<{ status: number; data: T } | NetworkError> {
|
|
const headers = new Headers(options.headers || {});
|
|
headers.set('apikey', API_KEY);
|
|
headers.set('Content-Type', 'application/json');
|
|
|
|
if (!isPublic) {
|
|
try {
|
|
const authSession = localStorage.getItem('supabase.auth.token');
|
|
if (authSession) {
|
|
const session = JSON.parse(authSession);
|
|
// A estrutura do token pode variar, ajuste se necessário
|
|
const token = session?.currentSession?.access_token || session?.access_token;
|
|
if (token) {
|
|
headers.set('Authorization', `Bearer ${token}`);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Falha ao ler o token de autenticação do localStorage.", error);
|
|
}
|
|
}
|
|
|
|
const config: RequestInit = { ...options, headers };
|
|
|
|
try {
|
|
const response = await fetch(`${BASE_URL}${endpoint}`, config);
|
|
|
|
if (response.status === 204) {
|
|
return { status: response.status, data: undefined as T };
|
|
}
|
|
|
|
const data = await response.json().catch(() => ({}));
|
|
|
|
return {
|
|
status: response.status,
|
|
data: data as T,
|
|
};
|
|
} catch (error) {
|
|
if (error instanceof Error) {
|
|
return { status: 'network_error', error };
|
|
}
|
|
return { status: 'network_error', error: new Error('Erro de rede desconhecido') };
|
|
}
|
|
}
|
|
|
|
// V4 CHANGE: Adicionamos uma asserção de tipo `as Promise<...>` em cada função.
|
|
// Isso informa ao TypeScript que confiamos que o retorno do apiClient corresponderá
|
|
// à união discriminada específica que definimos para cada endpoint.
|
|
|
|
// --- SERVIÇOS DE AUTENTICAÇÃO ---
|
|
export const authService = {
|
|
login: (credentials: { email: string; password: string }): Promise<LoginResponse> => {
|
|
return apiClient('/auth/v1/token?grant_type=password', {
|
|
method: 'POST',
|
|
body: JSON.stringify(credentials),
|
|
}, true) as Promise<LoginResponse>;
|
|
},
|
|
sendMagicLink: (email: string): Promise<SendMagicLinkResponse> => {
|
|
return apiClient('/auth/v1/otp', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ email }),
|
|
}, true) as Promise<SendMagicLinkResponse>;
|
|
},
|
|
logout: (): Promise<LogoutResponse> => {
|
|
return apiClient('/auth/v1/logout', { method: 'POST' }) as Promise<LogoutResponse>;
|
|
},
|
|
getCurrentUser: (): Promise<GetCurrentUserResponse> => {
|
|
return apiClient('/functions/v1/user-info', { method: 'POST' }) as Promise<GetCurrentUserResponse>;
|
|
},
|
|
};
|
|
|
|
// --- SERVIÇOS DE USUÁRIOS ---
|
|
export const userService = {
|
|
requestPasswordReset: (email: string, redirectUrl?: string): Promise<RequestPasswordResetResponse> => {
|
|
return apiClient('/request-password-reset', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ email, redirect_url: redirectUrl }),
|
|
}, true) as Promise<RequestPasswordResetResponse>;
|
|
},
|
|
createUserWithPassword: (payload: object): Promise<CreateUserWithPasswordResponse> => {
|
|
return apiClient('/create-user-with-password', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
}) as Promise<CreateUserWithPasswordResponse>;
|
|
},
|
|
hardDeleteUser_DANGEROUS: (userId: string): Promise<HardDeleteUserResponse> => {
|
|
return apiClient('/delete-user', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ userId }),
|
|
}) as Promise<HardDeleteUserResponse>;
|
|
},
|
|
deactivateUser: (userId: string): Promise<any> => {
|
|
return apiClient(`/rest/v1/profiles?id=eq.${userId}`, {
|
|
method: 'PATCH',
|
|
headers: { Prefer: 'return=representation' },
|
|
body: JSON.stringify({ disabled: true }),
|
|
});
|
|
}
|
|
};
|
|
|
|
// --- SERVIÇOS DE PACIENTES ---
|
|
export const patientService = {
|
|
registerPatient: (payload: { email: string; full_name: string; phone_mobile: string; cpf: string; birth_date?: string }): Promise<RegisterPatientResponse> => {
|
|
return apiClient('/functions/v1/register-patient', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
}, true) as Promise<RegisterPatientResponse>;
|
|
},
|
|
list: (filters: { fullName?: string; cpf?: string; limit?: number; offset?: number } = {}): Promise<ListResponse<Patient>> => {
|
|
const query = new URLSearchParams();
|
|
if (filters.fullName) query.set('full_name', `ilike.*${filters.fullName}*`);
|
|
if (filters.cpf) query.set('cpf', `eq.${filters.cpf}`);
|
|
if (filters.limit) query.set('limit', String(filters.limit));
|
|
if (filters.offset) query.set('offset', String(filters.offset));
|
|
return apiClient(`/rest/v1/patients?${query.toString()}`) as Promise<ListResponse<Patient>>;
|
|
},
|
|
create: (payload: Omit<Patient, 'id' | 'created_at' | 'updated_at'>): Promise<any> => {
|
|
return apiClient('/rest/v1/patients', {
|
|
method: 'POST',
|
|
headers: { Prefer: 'return=representation' },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
},
|
|
};
|
|
|
|
// --- SERVIÇOS DE MÉDICOS ---
|
|
export const doctorService = {
|
|
list: (filters: { specialty?: string; active?: boolean } = {}): Promise<ListResponse<Doctor>> => {
|
|
const query = new URLSearchParams({ select: '*' });
|
|
if (filters.specialty) query.set('specialty', `eq.${filters.specialty}`);
|
|
if (filters.active !== undefined) query.set('active', `eq.${filters.active}`);
|
|
return apiClient(`/rest/v1/doctors?${query.toString()}`) as Promise<ListResponse<Doctor>>;
|
|
},
|
|
};
|
|
|
|
// --- SERVIÇOS DE AGENDAMENTO E DISPONIBILIDADE ---
|
|
export const scheduleService = {
|
|
getAvailableSlots: (doctorId: string, date: string): Promise<GetAvailableSlotsResponse> => {
|
|
return apiClient('/functions/v1/get-available-slots', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ doctor_id: doctorId, date }),
|
|
}) as Promise<GetAvailableSlotsResponse>;
|
|
},
|
|
createAppointment: (payload: NewAppointmentPayload): Promise<CreateAppointmentResponse> => {
|
|
return apiClient('/rest/v1/appointments', {
|
|
method: 'POST',
|
|
headers: { Prefer: 'return=representation' },
|
|
body: JSON.stringify(payload),
|
|
}) as Promise<CreateAppointmentResponse>;
|
|
},
|
|
listAppointments: (filters: { doctorId?: string; patientId?: string; status?: string }): Promise<ListResponse<Appointment>> => {
|
|
const query = new URLSearchParams();
|
|
if (filters.doctorId) query.set('doctor_id', `eq.${filters.doctorId}`);
|
|
if (filters.patientId) query.set('patient_id', `eq.${filters.patientId}`);
|
|
if (filters.status) query.set('status', `eq.${filters.status}`);
|
|
return apiClient(`/rest/v1/appointments?${query.toString()}`) as Promise<ListResponse<Appointment>>;
|
|
},
|
|
cancelAppointment: (appointmentId: string, reason: string): Promise<CancelAppointmentResponse> => {
|
|
return apiClient(`/rest/v1/appointments?id=eq.${appointmentId}`, {
|
|
method: 'PATCH',
|
|
headers: { Prefer: 'return=representation' },
|
|
body: JSON.stringify({
|
|
status: 'cancelled',
|
|
cancellation_reason: reason,
|
|
cancelled_at: new Date().toISOString(),
|
|
}),
|
|
}) as Promise<CancelAppointmentResponse>;
|
|
},
|
|
listAvailability: (filters: { doctorId?: string } = {}): Promise<ListResponse<DoctorAvailability>> => {
|
|
const query = new URLSearchParams();
|
|
if (filters.doctorId) query.set('doctor_id', `eq.${filters.doctorId}`);
|
|
return apiClient(`/rest/v1/doctor_availability?${query.toString()}`) as Promise<ListResponse<DoctorAvailability>>;
|
|
},
|
|
listExceptions: (filters: { doctorId?: string; date?: string } = {}): Promise<ListResponse<DoctorException>> => {
|
|
const query = new URLSearchParams();
|
|
if (filters.doctorId) query.set('doctor_id', `eq.${filters.doctorId}`);
|
|
if (filters.date) query.set('date', `eq.${filters.date}`);
|
|
return apiClient(`/rest/v1/doctor_exceptions?${query.toString()}`) as Promise<ListResponse<DoctorException>>;
|
|
},
|
|
};
|
|
|
|
// --- SERVIÇOS DE LAUDOS (REPORTS) ---
|
|
export const reportService = {
|
|
list: (filters: { patientId?: string; createdBy?: string }): Promise<ListResponse<Report>> => {
|
|
const query = new URLSearchParams({ order: 'created_at.desc' });
|
|
if (filters.patientId) query.set('patient_id', `eq.${filters.patientId}`);
|
|
if (filters.createdBy) query.set('created_by', `eq.${filters.createdBy}`);
|
|
return apiClient(`/rest/v1/reports?${query.toString()}`) as Promise<ListResponse<Report>>;
|
|
},
|
|
create: (payload: ReportInput): Promise<CreateReportResponse> => {
|
|
return apiClient('/rest/v1/reports', {
|
|
method: 'POST',
|
|
headers: { Prefer: 'return=representation' },
|
|
body: JSON.stringify(payload),
|
|
}) as Promise<CreateReportResponse>;
|
|
},
|
|
update: (reportId: string, payload: Partial<ReportInput>): Promise<UpdateReportResponse> => {
|
|
return apiClient(`/rest/v1/reports?id=eq.${reportId}`, {
|
|
method: 'PATCH',
|
|
headers: { Prefer: 'return=representation' },
|
|
body: JSON.stringify(payload),
|
|
}) as Promise<UpdateReportResponse>;
|
|
},
|
|
}; |