guisilvagomes 6b9bfbbd29 feat: implementa sistema de agendamento com API de slots
- Adiciona Edge Function para calcular slots disponíveis
- Implementa método callFunction() no apiClient para Edge Functions
- Atualiza appointmentService com getAvailableSlots() e create()
- Simplifica AgendamentoConsulta removendo lógica manual de slots
- Remove arquivos de teste e documentação temporária
- Atualiza README com documentação completa
- Adiciona AGENDAMENTO-SLOTS-API.md com detalhes da implementação
- Corrige formatação de dados (telefone, CPF, nomes)
- Melhora diálogos de confirmação e feedback visual
- Otimiza performance e user experience
2025-10-30 12:56:52 -03:00

325 lines
8.6 KiB
TypeScript

/**
* Serviço de Usuários
*/
import axios from "axios";
import { apiClient } from "../api/client";
import { API_CONFIG } from "../api/config";
import type {
UserRoleRecord,
UserInfo,
User,
CreateUserInput,
CreateUserResponse,
CreateDoctorInput,
CreateDoctorResponse,
CreatePatientInput,
CreatePatientResponse,
} from "./types";
class UserService {
/**
* Lista roles de usuários
*/
async listRoles(): Promise<UserRoleRecord[]> {
const response = await apiClient.get<UserRoleRecord[]>("/user_roles");
return response.data;
}
/**
* Obtém informações completas do usuário autenticado
* Inclui perfil, roles e permissões calculadas
*/
async getUserInfo(): Promise<UserInfo> {
const token = localStorage.getItem(API_CONFIG.STORAGE_KEYS.ACCESS_TOKEN);
if (!token) {
throw new Error("Token não encontrado");
}
const response = await axios.post<UserInfo>(
`${API_CONFIG.FUNCTIONS_URL}/user-info`,
{},
{
headers: {
"Content-Type": "application/json",
apikey: API_CONFIG.SUPABASE_ANON_KEY,
Authorization: `Bearer ${token}`,
},
}
);
return response.data;
}
/**
* Obtém dados básicos do usuário autenticado
*/
async getCurrentUser(): Promise<User> {
const token = localStorage.getItem(API_CONFIG.STORAGE_KEYS.ACCESS_TOKEN);
if (!token) {
throw new Error("Token não encontrado");
}
const response = await axios.get<User>(`${API_CONFIG.AUTH_URL}/user`, {
headers: {
"Content-Type": "application/json",
apikey: API_CONFIG.SUPABASE_ANON_KEY,
Authorization: `Bearer ${token}`,
},
});
return response.data;
}
/**
* Cria novo usuário no sistema
* Pode ser chamado COM ou SEM autenticação:
* - SEM autenticação: usa signup nativo (/auth/v1/signup) - PÚBLICO
* - COM autenticação: usa Edge Function (/functions/v1/create-user) - ADMIN
*/
async createUser(
data: CreateUserInput,
isPublicRegistration: boolean = true
): Promise<CreateUserResponse> {
// Registro público: usar endpoint nativo do Supabase
if (isPublicRegistration) {
const response = await axios.post<{
user: User;
session: { access_token: string; refresh_token: string };
}>(
`${API_CONFIG.AUTH_URL}/signup`,
{
email: data.email,
password: data.password,
options: {
data: {
full_name: data.full_name,
phone: data.phone,
},
},
},
{
headers: {
"Content-Type": "application/json",
apikey: API_CONFIG.SUPABASE_ANON_KEY,
},
}
);
return {
success: true,
user: {
id: response.data.user.id,
email: response.data.user.email,
full_name: data.full_name,
phone: data.phone || null,
roles: [data.role],
},
message: "Usuário criado com sucesso",
};
}
// Criação por admin: usar Edge Function
const token = localStorage.getItem(API_CONFIG.STORAGE_KEYS.ACCESS_TOKEN);
const response = await axios.post<CreateUserResponse>(
`${API_CONFIG.FUNCTIONS_URL}/create-user`,
data,
{
headers: {
"Content-Type": "application/json",
apikey: API_CONFIG.SUPABASE_ANON_KEY,
Authorization: `Bearer ${token}`,
},
}
);
return response.data;
}
/**
* Adiciona uma role a um usuário
* Requer permissão de admin
*/
async addUserRole(userId: string, role: string): Promise<UserRoleRecord> {
const response = await apiClient.post<UserRoleRecord>("/user_roles", {
user_id: userId,
role: role,
});
return response.data;
}
/**
* Remove uma role de um usuário
* Requer permissão de admin
*/
async removeUserRole(userId: string, role: string): Promise<void> {
await apiClient.delete(`/user_roles?user_id=eq.${userId}&role=eq.${role}`);
}
/**
* Cria novo médico no sistema (endpoint especializado)
* Cria: Auth user + Profile + Role medico + Entrada completa em doctors
* Obrigatório: crm, crm_uf, cpf, full_name, email
* Validações: CRM válido, CPF válido, UF válido
* Use quando tiver TODOS os dados do médico
*/
async createDoctor(data: CreateDoctorInput): Promise<CreateDoctorResponse> {
const token = localStorage.getItem(API_CONFIG.STORAGE_KEYS.ACCESS_TOKEN);
const url = `${API_CONFIG.FUNCTIONS_URL}/create-doctor`;
console.log("[userService.createDoctor] URL:", url);
console.log("[userService.createDoctor] Data:", data);
const response = await axios.post<CreateDoctorResponse>(
url,
data,
{
headers: {
"Content-Type": "application/json",
apikey: API_CONFIG.SUPABASE_ANON_KEY,
Authorization: `Bearer ${token}`,
},
}
);
return response.data;
}
/**
* Cria novo paciente no sistema (endpoint especializado)
* Cria: Auth user + Profile + Role paciente + Entrada completa em patients
* Obrigatório: cpf, full_name, email, phone_mobile
* Validações: CPF válido, telefone válido
* Use quando tiver TODOS os dados do paciente
*/
async createPatient(
data: CreatePatientInput
): Promise<CreatePatientResponse> {
const token = localStorage.getItem(API_CONFIG.STORAGE_KEYS.ACCESS_TOKEN);
const url = `${API_CONFIG.FUNCTIONS_URL}/create-patient`;
console.log("[userService.createPatient] URL:", url);
console.log("[userService.createPatient] Data:", data);
const response = await axios.post<CreatePatientResponse>(
url,
data,
{
headers: {
"Content-Type": "application/json",
apikey: API_CONFIG.SUPABASE_ANON_KEY,
Authorization: `Bearer ${token}`,
},
}
);
return response.data;
}
/**
* Cria usuário com email e senha (alternativa ao Magic Link)
* POST /functions/v1/create-user-with-password
* Requer permissão de admin, gestor ou secretaria
* O usuário precisa confirmar o email antes de fazer login
*/
async createUserWithPassword(data: {
email: string;
password: string;
full_name: string;
phone?: string;
role: string;
create_patient_record?: boolean;
cpf?: string;
phone_mobile?: string;
}): Promise<{
success: boolean;
user: {
id: string;
email: string;
full_name: string;
roles: string[];
email_confirmed_at: string | null;
patient_id?: string;
};
message: string;
}> {
const token = localStorage.getItem(API_CONFIG.STORAGE_KEYS.ACCESS_TOKEN);
const response = await axios.post(
`${API_CONFIG.FUNCTIONS_URL}/create-user-with-password`,
data,
{
headers: {
"Content-Type": "application/json",
apikey: API_CONFIG.SUPABASE_ANON_KEY,
Authorization: `Bearer ${token}`,
},
}
);
return response.data;
}
/**
* Deleta usuário permanentemente (Hard Delete)
* POST /delete-user
* ⚠️ OPERAÇÃO IRREVERSÍVEL! Use apenas em desenvolvimento/QA
* Requer permissão de admin ou gestor
* Deleta em cascata: profiles, user_roles, doctors, patients, etc.
*/
async deleteUser(userId: string): Promise<{
success: boolean;
message: string;
userId: string;
}> {
const token = localStorage.getItem(API_CONFIG.STORAGE_KEYS.ACCESS_TOKEN);
const response = await axios.post<{
success: boolean;
message: string;
userId: string;
}>(
`${API_CONFIG.FUNCTIONS_URL}/delete-user`,
{ userId },
{
headers: {
"Content-Type": "application/json",
apikey: API_CONFIG.SUPABASE_ANON_KEY,
Authorization: `Bearer ${token}`,
},
}
);
return response.data;
}
/**
* Obtém informações de usuário por ID
* POST /functions/v1/user-info-by-id
* Requer permissão de admin ou gestor
*/
async getUserInfoById(userId: string): Promise<UserInfo> {
const token = localStorage.getItem(API_CONFIG.STORAGE_KEYS.ACCESS_TOKEN);
if (!token) {
throw new Error("Token não encontrado");
}
const response = await axios.post<UserInfo>(
`${API_CONFIG.FUNCTIONS_URL}/user-info-by-id`,
{ user_id: userId },
{
headers: {
"Content-Type": "application/json",
apikey: API_CONFIG.SUPABASE_ANON_KEY,
Authorization: `Bearer ${token}`,
},
}
);
return response.data;
}
}
export const userService = new UserService();