diff --git a/MEDICONNECT 2/add-fernando-patient.cjs b/MEDICONNECT 2/add-fernando-patient.cjs new file mode 100644 index 000000000..8dfe8cf2f --- /dev/null +++ b/MEDICONNECT 2/add-fernando-patient.cjs @@ -0,0 +1,76 @@ +const axios = require("axios"); + +const ANON_KEY = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; +const BASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; + +(async () => { + try { + console.log("🔐 Fazendo login como admin..."); + const loginRes = await axios.post( + `${BASE_URL}/auth/v1/token?grant_type=password`, + { + email: "riseup@popcode.com.br", + password: "riseup", + }, + { + headers: { + "Content-Type": "application/json", + apikey: ANON_KEY, + }, + } + ); + + console.log("✅ Login admin bem-sucedido!\n"); + const token = loginRes.data.access_token; + + // Buscar o ID do Fernando no profiles + console.log("🔍 Buscando ID do Fernando..."); + const profileRes = await axios.get( + `${BASE_URL}/rest/v1/profiles?email=eq.fernando.pirichowski@souunit.com.br&select=*`, + { + headers: { + apikey: ANON_KEY, + Authorization: `Bearer ${token}`, + }, + } + ); + + if (profileRes.data.length === 0) { + console.log("❌ Fernando não encontrado no profiles"); + return; + } + + const fernandoId = profileRes.data[0].id; + console.log("✅ Fernando encontrado! ID:", fernandoId); + + // Criar entrada na tabela patients + console.log("\n📋 Criando entrada na tabela patients..."); + const patientRes = await axios.post( + `${BASE_URL}/rest/v1/patients`, + { + id: fernandoId, + email: "fernando.pirichowski@souunit.com.br", + full_name: "Fernando Pirichowski", + phone_mobile: "51999999999", + cpf: "12345678909", // CPF válido fictício + }, + { + headers: { + "Content-Type": "application/json", + apikey: ANON_KEY, + Authorization: `Bearer ${token}`, + Prefer: "return=representation", + }, + } + ); + + console.log("✅ Entrada na tabela patients criada!"); + console.log("\n🎉 Usuário Fernando Pirichowski completo!"); + console.log("📧 Email: fernando.pirichowski@souunit.com.br"); + console.log("🔑 Senha: fernando123"); + console.log("\n✨ Agora você pode testar a recuperação de senha!"); + } catch (err) { + console.error("❌ Erro:", err.response?.data || err.message); + } +})(); diff --git a/MEDICONNECT 2/check-fernando.js b/MEDICONNECT 2/check-fernando.js new file mode 100644 index 000000000..cd3c4cac1 --- /dev/null +++ b/MEDICONNECT 2/check-fernando.js @@ -0,0 +1,58 @@ +const axios = require("axios"); + +const ANON_KEY = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; +const BASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; + +(async () => { + try { + console.log("🔐 Fazendo login como admin..."); + const loginRes = await axios.post( + `${BASE_URL}/auth/v1/token?grant_type=password`, + { + email: "riseup@popcode.com.br", + password: "riseup", + }, + { + headers: { + "Content-Type": "application/json", + apikey: ANON_KEY, + }, + } + ); + + console.log("✅ Login admin bem-sucedido!"); + const token = loginRes.data.access_token; + + console.log("\n🔍 Buscando usuário fernando..."); + const usersRes = await axios.get(`${BASE_URL}/rest/v1/profiles?select=*`, { + headers: { + apikey: ANON_KEY, + Authorization: `Bearer ${token}`, + }, + }); + + console.log(`\n📊 Total de usuários: ${usersRes.data.length}`); + + const fernando = usersRes.data.find( + (u) => + u.email && + (u.email.toLowerCase().includes("fernando") || + u.full_name?.toLowerCase().includes("fernando")) + ); + + if (fernando) { + console.log("\n✅ Usuário Fernando encontrado:"); + console.log(JSON.stringify(fernando, null, 2)); + } else { + console.log("\n❌ Usuário Fernando NÃO encontrado na tabela profiles"); + console.log("\n📧 Alguns emails cadastrados:"); + usersRes.data.slice(0, 15).forEach((u) => { + if (u.email) + console.log(` - ${u.email} (${u.full_name || "sem nome"})`); + }); + } + } catch (err) { + console.error("❌ Erro:", err.response?.data || err.message); + } +})(); diff --git a/MEDICONNECT 2/create-fernando.cjs b/MEDICONNECT 2/create-fernando.cjs new file mode 100644 index 000000000..83c5cfbef --- /dev/null +++ b/MEDICONNECT 2/create-fernando.cjs @@ -0,0 +1,87 @@ +const axios = require("axios"); + +const ANON_KEY = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; +const BASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; + +(async () => { + try { + console.log("🔐 Fazendo login como admin..."); + const loginRes = await axios.post( + `${BASE_URL}/auth/v1/token?grant_type=password`, + { + email: "riseup@popcode.com.br", + password: "riseup", + }, + { + headers: { + "Content-Type": "application/json", + apikey: ANON_KEY, + }, + } + ); + + console.log("✅ Login admin bem-sucedido!\n"); + const token = loginRes.data.access_token; + + console.log("👤 Criando usuário Fernando Pirichowski..."); + + // Criar usuário via signup + const signupRes = await axios.post( + `${BASE_URL}/auth/v1/signup`, + { + email: "fernando.pirichowski@souunit.com.br", + password: "fernando123", // Senha temporária + options: { + data: { + full_name: "Fernando Pirichowski", + phone: "51999999999", + }, + }, + }, + { + headers: { + "Content-Type": "application/json", + apikey: ANON_KEY, + }, + } + ); + + console.log("✅ Usuário criado com sucesso!"); + console.log("📧 Email:", signupRes.data.user.email); + console.log("🆔 ID:", signupRes.data.user.id); + console.log("🔑 Senha temporária: fernando123\n"); + + // Criar entrada na tabela patients + console.log("📋 Criando entrada na tabela patients..."); + const patientRes = await axios.post( + `${BASE_URL}/rest/v1/patients`, + { + id: signupRes.data.user.id, + email: "fernando.pirichowski@souunit.com.br", + full_name: "Fernando Pirichowski", + phone_mobile: "51999999999", + cpf: "12345678909", // CPF válido fictício + }, + { + headers: { + "Content-Type": "application/json", + apikey: ANON_KEY, + Authorization: `Bearer ${token}`, + Prefer: "return=representation", + }, + } + ); + + console.log("✅ Entrada na tabela patients criada!"); + console.log("\n🎉 Usuário Fernando Pirichowski criado com sucesso!"); + console.log("📧 Email: fernando.pirichowski@souunit.com.br"); + console.log("🔑 Senha: fernando123"); + console.log("\n💡 Agora você pode testar a recuperação de senha!"); + } catch (err) { + console.error("❌ Erro:", err.response?.data || err.message); + if (err.response?.data?.msg) { + console.error("Mensagem:", err.response.data.msg); + } + } +})(); diff --git a/MEDICONNECT 2/index.html b/MEDICONNECT 2/index.html index deaa41ce4..df371ea8b 100644 --- a/MEDICONNECT 2/index.html +++ b/MEDICONNECT 2/index.html @@ -1,11 +1,42 @@ - - + - + - MediConnect + MediConnect - Sistema de Agendamento Médico + + + + + + + + + + + + + +
diff --git a/MEDICONNECT 2/netlify.toml b/MEDICONNECT 2/netlify.toml deleted file mode 100644 index 7f2c6815c..000000000 --- a/MEDICONNECT 2/netlify.toml +++ /dev/null @@ -1,24 +0,0 @@ -[build] - command = "pnpm build" - publish = "dist" - -[functions] - directory = "netlify/functions" - -[dev] - command = "npm run dev" - targetPort = 5173 - port = 8888 - autoLaunch = false - framework = "#custom" - -[[redirects]] - from = "/*" - to = "/index.html" - status = 200 - -# Optional: control caching of static assets -[[headers]] - for = "/assets/*" - [headers.values] - Cache-Control = "public, max-age=31536000, immutable" diff --git a/MEDICONNECT 2/netlify/functions/appointments.ts b/MEDICONNECT 2/netlify/functions/appointments.ts deleted file mode 100644 index 0eaab64df..000000000 --- a/MEDICONNECT 2/netlify/functions/appointments.ts +++ /dev/null @@ -1,163 +0,0 @@ -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { statusCode: 200, headers, body: "" }; - } - - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - if (!authHeader) { - return { - statusCode: 401, - headers, - body: JSON.stringify({ error: "Token n�o fornecido" }), - }; - } - - const pathParts = event.path.split("/"); - const appointmentId = - pathParts[pathParts.length - 1] !== "appointments" - ? pathParts[pathParts.length - 1] - : null; - - if (event.httpMethod === "GET") { - let url = `${SUPABASE_URL}/rest/v1/appointments`; - if (appointmentId && appointmentId !== "appointments") { - url += `?id=eq.${appointmentId}&select=*`; - } else if (event.queryStringParameters) { - const params = new URLSearchParams( - event.queryStringParameters as Record - ); - url += `?${params.toString()}`; - if (!params.has("select")) { - url += url.includes("?") ? "&select=*" : "?select=*"; - } - } else { - url += "?select=*"; - } - - const response = await fetch(url, { - method: "GET", - headers: { apikey: SUPABASE_ANON_KEY, Authorization: authHeader }, - }); - let data = await response.json(); - if ( - appointmentId && - appointmentId !== "appointments" && - Array.isArray(data) && - data.length > 0 - ) { - data = data[0]; - } - return { - statusCode: response.status, - headers: { ...headers, "Content-Type": "application/json" }, - body: JSON.stringify(data), - }; - } - - if (event.httpMethod === "POST") { - const body = JSON.parse(event.body || "{}"); - if (!body.patient_id || !body.doctor_id || !body.scheduled_at) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ - error: "Campos obrigat�rios: patient_id, doctor_id, scheduled_at", - }), - }; - } - const response = await fetch(`${SUPABASE_URL}/rest/v1/appointments`, { - method: "POST", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - Prefer: "return=representation", - }, - body: JSON.stringify(body), - }); - let data = await response.json(); - if (Array.isArray(data) && data.length > 0) data = data[0]; - return { - statusCode: response.status, - headers: { ...headers, "Content-Type": "application/json" }, - body: JSON.stringify(data), - }; - } - - if (event.httpMethod === "PATCH") { - if (!appointmentId || appointmentId === "appointments") { - return { - statusCode: 400, - headers, - body: JSON.stringify({ error: "ID do agendamento � obrigat�rio" }), - }; - } - const body = JSON.parse(event.body || "{}"); - const response = await fetch( - `${SUPABASE_URL}/rest/v1/appointments?id=eq.${appointmentId}`, - { - method: "PATCH", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - Prefer: "return=representation", - }, - body: JSON.stringify(body), - } - ); - let data = await response.json(); - if (Array.isArray(data) && data.length > 0) data = data[0]; - return { - statusCode: response.status, - headers: { ...headers, "Content-Type": "application/json" }, - body: JSON.stringify(data), - }; - } - - if (event.httpMethod === "DELETE") { - if (!appointmentId || appointmentId === "appointments") { - return { - statusCode: 400, - headers, - body: JSON.stringify({ error: "ID do agendamento � obrigat�rio" }), - }; - } - const response = await fetch( - `${SUPABASE_URL}/rest/v1/appointments?id=eq.${appointmentId}`, - { - method: "DELETE", - headers: { apikey: SUPABASE_ANON_KEY, Authorization: authHeader }, - } - ); - return { statusCode: response.status, headers, body: "" }; - } - - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } catch (error) { - console.error("Erro:", error); - return { - statusCode: 500, - headers, - body: JSON.stringify({ error: "Erro interno" }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/assignments.ts b/MEDICONNECT 2/netlify/functions/assignments.ts deleted file mode 100644 index 1869ebcbf..000000000 --- a/MEDICONNECT 2/netlify/functions/assignments.ts +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Netlify Function: Listar Atribuições - * GET /rest/v1/patient_assignments - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "GET, POST, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - // GET - Listar atribuições - if (event.httpMethod === "GET") { - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - - if (!authHeader) { - return { - statusCode: 401, - headers, - body: JSON.stringify({ error: "Token não fornecido" }), - }; - } - - // Monta URL com query params (se houver) - const queryString = event.queryStringParameters - ? "?" + - new URLSearchParams( - event.queryStringParameters as Record - ).toString() - : ""; - - const response = await fetch( - `${SUPABASE_URL}/rest/v1/patient_assignments${queryString}`, - { - method: "GET", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - }, - } - ); - - const data = await response.json(); - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } catch (error) { - console.error("Erro ao listar atribuições:", error); - - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } - } - - // POST - Criar atribuição - if (event.httpMethod === "POST") { - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - - if (!authHeader) { - return { - statusCode: 401, - headers, - body: JSON.stringify({ error: "Token não fornecido" }), - }; - } - - const body = JSON.parse(event.body || "{}"); - - if (!body.patient_id || !body.user_id || !body.role) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ - error: "patient_id, user_id e role são obrigatórios", - }), - }; - } - - const response = await fetch( - `${SUPABASE_URL}/rest/v1/patient_assignments`, - { - method: "POST", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - Prefer: "return=representation", - }, - body: JSON.stringify(body), - } - ); - - const data = await response.json(); - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } catch (error) { - console.error("Erro ao criar atribuição:", error); - - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } - } - - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; -}; diff --git a/MEDICONNECT 2/netlify/functions/auth-login.ts b/MEDICONNECT 2/netlify/functions/auth-login.ts index b4f76a5ab..90293e6dc 100644 --- a/MEDICONNECT 2/netlify/functions/auth-login.ts +++ b/MEDICONNECT 2/netlify/functions/auth-login.ts @@ -46,6 +46,12 @@ export const handler: Handler = async (event: HandlerEvent) => { const body: LoginRequest = JSON.parse(event.body || "{}"); if (!body.email || !body.password) { + // Log headers and raw body to help debugging malformed requests from frontend + console.error( + "[auth-login] Requisição inválida - falta email ou password. Headers:", + event.headers + ); + console.error("[auth-login] Raw body:", event.body); return { statusCode: 400, headers, diff --git a/MEDICONNECT 2/netlify/functions/auth-logout.ts b/MEDICONNECT 2/netlify/functions/auth-logout.ts deleted file mode 100644 index 1854837c9..000000000 --- a/MEDICONNECT 2/netlify/functions/auth-logout.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Netlify Function: Logout - * Invalida a sessão do usuário no Supabase - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "POST, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - if (event.httpMethod !== "POST") { - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } - - try { - // Pega o Bearer token do header - const authHeader = - event.headers.authorization || event.headers.Authorization; - - if (!authHeader) { - return { - statusCode: 401, - headers, - body: JSON.stringify({ error: "Token não fornecido" }), - }; - } - - // Faz logout no Supabase - const response = await fetch(`${SUPABASE_URL}/auth/v1/logout`, { - method: "POST", - headers: { - "Content-Type": "application/json", - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - }, - }); - - // Logout retorna 204 No Content (sem body) - if (response.status === 204) { - return { - statusCode: 204, - headers, - body: "", - }; - } - - // Se não for 204, retorna o body da resposta - const data = await response.text(); - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: data || "{}", - }; - } catch (error) { - console.error("Erro no logout:", error); - - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/auth-magic-link.ts b/MEDICONNECT 2/netlify/functions/auth-magic-link.ts deleted file mode 100644 index bf2d32bf4..000000000 --- a/MEDICONNECT 2/netlify/functions/auth-magic-link.ts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Netlify Function: Magic Link - * Envia link de autenticação sem senha por email - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -// Constantes da API (protegidas no backend) -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -interface MagicLinkRequest { - email: string; - redirect_url?: string; -} - -export const handler: Handler = async (event: HandlerEvent) => { - // CORS headers - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "POST, OPTIONS", - }; - - // Handle preflight - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - // Apenas POST é permitido - if (event.httpMethod !== "POST") { - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } - - try { - // Parse body - const body: MagicLinkRequest = JSON.parse(event.body || "{}"); - - if (!body.email) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ error: "Email é obrigatório" }), - }; - } - - // Faz requisição para API Supabase COM a apikey protegida - const response = await fetch(`${SUPABASE_URL}/auth/v1/otp`, { - method: "POST", - headers: { - "Content-Type": "application/json", - apikey: SUPABASE_ANON_KEY, - }, - body: JSON.stringify({ - email: body.email, - options: { - emailRedirectTo: - body.redirect_url || - "https://mediconnectbrasil.netlify.app/auth/callback", - }, - }), - }); - - const data = await response.json(); - - // Repassa a resposta para o frontend - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } catch (error) { - console.error("[auth-magic-link] Erro:", error); - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro ao enviar magic link", - details: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/auth-refresh.ts b/MEDICONNECT 2/netlify/functions/auth-refresh.ts deleted file mode 100644 index 84eca6a30..000000000 --- a/MEDICONNECT 2/netlify/functions/auth-refresh.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Netlify Function: Refresh Token - * Renova o access token usando o refresh token - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -interface RefreshTokenRequest { - refresh_token: string; -} - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "POST, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - if (event.httpMethod !== "POST") { - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } - - try { - const body: RefreshTokenRequest = JSON.parse(event.body || "{}"); - - if (!body.refresh_token) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ error: "Refresh token é obrigatório" }), - }; - } - - // Faz requisição para renovar token no Supabase - const response = await fetch( - `${SUPABASE_URL}/auth/v1/token?grant_type=refresh_token`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - apikey: SUPABASE_ANON_KEY, - }, - body: JSON.stringify({ - refresh_token: body.refresh_token, - }), - } - ); - - const data = await response.json(); - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } catch (error) { - console.error("Erro ao renovar token:", error); - - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/auth-user.ts b/MEDICONNECT 2/netlify/functions/auth-user.ts deleted file mode 100644 index 1203887e5..000000000 --- a/MEDICONNECT 2/netlify/functions/auth-user.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Netlify Function: Auth User - * GET /auth/v1/user - Retorna dados do usuário autenticado - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "GET, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - - if (!authHeader) { - return { - statusCode: 401, - headers, - body: JSON.stringify({ error: "Token não fornecido" }), - }; - } - - if (event.httpMethod === "GET") { - const response = await fetch(`${SUPABASE_URL}/auth/v1/user`, { - method: "GET", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - }, - }); - - const data = await response.json(); - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } - - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } catch (error) { - console.error("Erro na API de auth user:", error); - - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/avatars-delete.ts b/MEDICONNECT 2/netlify/functions/avatars-delete.ts deleted file mode 100644 index 13e18cb94..000000000 --- a/MEDICONNECT 2/netlify/functions/avatars-delete.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Netlify Function: Delete Avatar - * DELETE /storage/v1/object/avatars/{userId}/avatar - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "DELETE, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - if (event.httpMethod !== "DELETE") { - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } - - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - - if (!authHeader) { - return { - statusCode: 401, - headers, - body: JSON.stringify({ error: "Token não fornecido" }), - }; - } - - const userId = event.queryStringParameters?.userId; - - if (!userId) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ error: "userId é obrigatório" }), - }; - } - - const response = await fetch( - `${SUPABASE_URL}/storage/v1/object/avatars/${userId}/avatar`, - { - method: "DELETE", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - }, - } - ); - - // DELETE pode retornar 200 com body vazio - const contentType = response.headers.get("content-type"); - const data = contentType?.includes("application/json") - ? await response.json() - : {}; - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } catch (error) { - console.error("Erro ao deletar avatar:", error); - - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/avatars-upload.ts b/MEDICONNECT 2/netlify/functions/avatars-upload.ts deleted file mode 100644 index 854ee40cb..000000000 --- a/MEDICONNECT 2/netlify/functions/avatars-upload.ts +++ /dev/null @@ -1,145 +0,0 @@ -/** - * Netlify Function: Upload Avatar - * POST /storage/v1/object/avatars/{userId}/avatar - * - * Aceita JSON com base64 para simplificar o upload via Netlify Functions - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "POST, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - if (event.httpMethod !== "POST") { - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } - - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - - if (!authHeader) { - return { - statusCode: 401, - headers, - body: JSON.stringify({ error: "Token não fornecido" }), - }; - } - - // Extrai userId do query string - const userId = event.queryStringParameters?.userId; - - if (!userId) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ error: "userId é obrigatório" }), - }; - } - - // Parse JSON body com base64 - let fileData: string; - let contentType: string; - - try { - const body = JSON.parse(event.body || "{}"); - fileData = body.fileData; // base64 string - contentType = body.contentType || "image/jpeg"; - - if (!fileData) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ error: "fileData (base64) é obrigatório" }), - }; - } - } catch { - return { - statusCode: 400, - headers, - body: JSON.stringify({ - error: "Body deve ser JSON válido com fileData em base64", - }), - }; - } - - // Converte base64 para Buffer - const buffer = Buffer.from(fileData, "base64"); - - // Upload para Supabase Storage - const response = await fetch( - `${SUPABASE_URL}/storage/v1/object/avatars/${userId}/avatar`, - { - method: "POST", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": contentType, - "x-upsert": "true", // Sobrescreve se já existir - }, - body: buffer, - } - ); - - const data = await response.json(); - - if (!response.ok) { - console.error("Erro do Supabase:", data); - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - error: data.error || "Erro ao fazer upload no Supabase", - details: data, - }), - }; - } - - return { - statusCode: 200, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - message: "Upload realizado com sucesso", - path: data.Key || data.path, - fullPath: data.Key || data.path, - }), - }; - } catch (error) { - console.error("Erro no upload do avatar:", error); - - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/consultas.ts b/MEDICONNECT 2/netlify/functions/consultas.ts deleted file mode 100644 index b22e39ab9..000000000 --- a/MEDICONNECT 2/netlify/functions/consultas.ts +++ /dev/null @@ -1,163 +0,0 @@ -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { statusCode: 200, headers, body: "" }; - } - - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - if (!authHeader) { - return { - statusCode: 401, - headers, - body: JSON.stringify({ error: "Token n�o fornecido" }), - }; - } - - const pathParts = event.path.split("/"); - const appointmentId = - pathParts[pathParts.length - 1] !== "consultas" - ? pathParts[pathParts.length - 1] - : null; - - if (event.httpMethod === "GET") { - let url = `${SUPABASE_URL}/rest/v1/appointments`; - if (appointmentId && appointmentId !== "consultas") { - url += `?id=eq.${appointmentId}&select=*`; - } else if (event.queryStringParameters) { - const params = new URLSearchParams( - event.queryStringParameters as Record - ); - url += `?${params.toString()}`; - if (!params.has("select")) { - url += url.includes("?") ? "&select=*" : "?select=*"; - } - } else { - url += "?select=*"; - } - - const response = await fetch(url, { - method: "GET", - headers: { apikey: SUPABASE_ANON_KEY, Authorization: authHeader }, - }); - let data = await response.json(); - if ( - appointmentId && - appointmentId !== "consultas" && - Array.isArray(data) && - data.length > 0 - ) { - data = data[0]; - } - return { - statusCode: response.status, - headers: { ...headers, "Content-Type": "application/json" }, - body: JSON.stringify(data), - }; - } - - if (event.httpMethod === "POST") { - const body = JSON.parse(event.body || "{}"); - if (!body.patient_id || !body.doctor_id || !body.scheduled_at) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ - error: "Campos obrigat�rios: patient_id, doctor_id, scheduled_at", - }), - }; - } - const response = await fetch(`${SUPABASE_URL}/rest/v1/appointments`, { - method: "POST", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - Prefer: "return=representation", - }, - body: JSON.stringify(body), - }); - let data = await response.json(); - if (Array.isArray(data) && data.length > 0) data = data[0]; - return { - statusCode: response.status, - headers: { ...headers, "Content-Type": "application/json" }, - body: JSON.stringify(data), - }; - } - - if (event.httpMethod === "PATCH") { - if (!appointmentId || appointmentId === "consultas") { - return { - statusCode: 400, - headers, - body: JSON.stringify({ error: "ID do agendamento � obrigat�rio" }), - }; - } - const body = JSON.parse(event.body || "{}"); - const response = await fetch( - `${SUPABASE_URL}/rest/v1/appointments?id=eq.${appointmentId}`, - { - method: "PATCH", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - Prefer: "return=representation", - }, - body: JSON.stringify(body), - } - ); - let data = await response.json(); - if (Array.isArray(data) && data.length > 0) data = data[0]; - return { - statusCode: response.status, - headers: { ...headers, "Content-Type": "application/json" }, - body: JSON.stringify(data), - }; - } - - if (event.httpMethod === "DELETE") { - if (!appointmentId || appointmentId === "consultas") { - return { - statusCode: 400, - headers, - body: JSON.stringify({ error: "ID do agendamento � obrigat�rio" }), - }; - } - const response = await fetch( - `${SUPABASE_URL}/rest/v1/appointments?id=eq.${appointmentId}`, - { - method: "DELETE", - headers: { apikey: SUPABASE_ANON_KEY, Authorization: authHeader }, - } - ); - return { statusCode: response.status, headers, body: "" }; - } - - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } catch (error) { - console.error("Erro:", error); - return { - statusCode: 500, - headers, - body: JSON.stringify({ error: "Erro interno" }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/create-doctor.ts b/MEDICONNECT 2/netlify/functions/create-doctor.ts deleted file mode 100644 index a8ecad447..000000000 --- a/MEDICONNECT 2/netlify/functions/create-doctor.ts +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Netlify Function: Create Doctor - * POST /create-doctor - Cria registro de médico com validações - * Não cria auth user - apenas registro na tabela doctors - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "POST, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - if (event.httpMethod !== "POST") { - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } - - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - - if (!authHeader) { - return { - statusCode: 401, - headers, - body: JSON.stringify({ error: "Token não fornecido" }), - }; - } - - const body = JSON.parse(event.body || "{}"); - - // Validação dos campos obrigatórios - if ( - !body.email || - !body.full_name || - !body.cpf || - !body.crm || - !body.crm_uf - ) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ - error: "Campos obrigatórios: email, full_name, cpf, crm, crm_uf", - }), - }; - } - - // Chama a Edge Function do Supabase para criar médico - const response = await fetch(`${SUPABASE_URL}/functions/v1/create-doctor`, { - method: "POST", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - }, - body: JSON.stringify(body), - }); - - const data = await response.json(); - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } catch (error) { - console.error("Erro na API de create doctor:", error); - - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/create-patient.ts b/MEDICONNECT 2/netlify/functions/create-patient.ts deleted file mode 100644 index cc447906f..000000000 --- a/MEDICONNECT 2/netlify/functions/create-patient.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Netlify Function: Create Patient - * POST /create-patient - Cria registro de paciente diretamente - * Não cria auth user - apenas registro na tabela patients - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "POST, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - if (event.httpMethod !== "POST") { - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } - - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - - if (!authHeader) { - return { - statusCode: 401, - headers, - body: JSON.stringify({ error: "Token não fornecido" }), - }; - } - - const body = JSON.parse(event.body || "{}"); - - // Validação dos campos obrigatórios - if ( - !body.full_name || - !body.cpf || - !body.email || - !body.phone_mobile || - !body.created_by - ) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ - error: - "Campos obrigatórios: full_name, cpf, email, phone_mobile, created_by", - }), - }; - } - - // Chama REST API do Supabase para criar paciente diretamente - const response = await fetch(`${SUPABASE_URL}/rest/v1/patients`, { - method: "POST", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - Prefer: "return=representation", - }, - body: JSON.stringify(body), - }); - - const data = await response.json(); - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } catch (error) { - console.error("Erro na API de create patient:", error); - - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/create-user-with-password.ts b/MEDICONNECT 2/netlify/functions/create-user-with-password.ts deleted file mode 100644 index 882d3ece2..000000000 --- a/MEDICONNECT 2/netlify/functions/create-user-with-password.ts +++ /dev/null @@ -1,223 +0,0 @@ -/** - * Netlify Function: Create User With Password - * POST /create-user-with-password - Cria usuário com senha - * Usa Edge Function do Supabase (não Admin API) - * Requer permissão de admin, gestor ou secretaria - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization, apikey", - "Access-Control-Allow-Methods": "POST, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - if (event.httpMethod !== "POST") { - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } - - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - - if (!authHeader) { - console.error("[create-user-with-password] Token não fornecido!"); - return { - statusCode: 401, - headers, - body: JSON.stringify({ - error: "Token de autenticação é obrigatório", - }), - }; - } - - const body = JSON.parse(event.body || "{}"); - - console.log( - "[create-user-with-password] Recebido:", - JSON.stringify({ ...body, password: "***" }, null, 2) - ); - - // Validações - if (!body.email || !body.password || !body.full_name) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ - error: "Campos obrigatórios: email, password, full_name", - }), - }; - } - - if (body.password.length < 6) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ - error: "Senha deve ter no mínimo 6 caracteres", - }), - }; - } - - // 1. Criar usuário via Edge Function do Supabase - console.log( - "[create-user-with-password] Chamando Edge Function do Supabase..." - ); - console.log( - "[create-user-with-password] URL:", - `${SUPABASE_URL}/functions/v1/create-user` - ); - console.log("[create-user-with-password] Payload:", { - email: body.email, - has_password: !!body.password, - full_name: body.full_name, - }); - - const createUserResponse = await fetch( - `${SUPABASE_URL}/functions/v1/create-user`, - { - method: "POST", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - email: body.email, - password: body.password, - full_name: body.full_name, - phone: body.phone || null, - role: body.role || "user", - }), - } - ); - - console.log( - "[create-user-with-password] Status da resposta:", - createUserResponse.status - ); - console.log( - "[create-user-with-password] Status text:", - createUserResponse.statusText - ); - - // Sempre tenta ler a resposta como JSON - let responseData; - try { - responseData = await createUserResponse.json(); - console.log( - "[create-user-with-password] Resposta JSON:", - JSON.stringify(responseData, null, 2) - ); - } catch (error) { - const responseText = await createUserResponse.text(); - console.error( - "[create-user-with-password] Resposta não é JSON:", - responseText - ); - console.error("[create-user-with-password] Erro ao parsear JSON:", error); - return { - statusCode: 500, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - error: "Erro ao processar resposta do Supabase", - details: responseText, - }), - }; - } - - if (!createUserResponse.ok) { - console.error( - "[create-user-with-password] Erro ao criar usuário:", - JSON.stringify(responseData, null, 2) - ); - return { - statusCode: createUserResponse.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - error: - responseData.msg || responseData.message || "Erro ao criar usuário", - details: responseData, - }), - }; - } - - // Verificar se a Edge Function retornou sucesso - if (!responseData.success) { - console.error( - "[create-user-with-password] Edge Function retornou erro:", - JSON.stringify(responseData, null, 2) - ); - return { - statusCode: 400, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - error: responseData.error || "Erro ao criar usuário", - details: responseData, - }), - }; - } - - const userData = responseData.user; - console.log( - "[create-user-with-password] Usuário criado com sucesso:", - userData.id - ); - console.log( - "[create-user-with-password] Resposta completa:", - JSON.stringify(responseData, null, 2) - ); - - // A Edge Function já cria o perfil e atribui a role automaticamente - // Retornar sucesso - return { - statusCode: 201, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - success: true, - user: userData, - message: responseData.message || "Usuário criado com sucesso", - }), - }; - } catch (error) { - console.error("[create-user-with-password] Erro:", error); - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/create-user.ts b/MEDICONNECT 2/netlify/functions/create-user.ts deleted file mode 100644 index eec83df16..000000000 --- a/MEDICONNECT 2/netlify/functions/create-user.ts +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Netlify Function: Create User - * POST /create-user - Cria novo usuário no sistema - * Requer permissão de admin, gestor ou secretaria - * Envia magic link automaticamente para o email - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "POST, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - - // create-user pode ser chamado SEM autenticação (para auto-registro) - // Se houver token, será usado; se não houver, usa apenas anon key - - if (event.httpMethod === "POST") { - const body = JSON.parse(event.body || "{}"); - - console.log( - "[create-user] Recebido body:", - JSON.stringify(body, null, 2) - ); - console.log("[create-user] Auth header presente?", !!authHeader); - - // Validação dos campos obrigatórios - if (!body.email || !body.full_name) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ - error: "Campos obrigatórios: email, full_name", - }), - }; - } - - if (!body.role && (!body.roles || body.roles.length === 0)) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ - error: "É necessário fornecer role ou roles", - }), - }; - } - - // Chama a Edge Function do Supabase para criar usuário - const fetchHeaders: Record = { - apikey: SUPABASE_ANON_KEY, - "Content-Type": "application/json", - // Se houver token de usuário autenticado, usa ele; senão usa anon key - Authorization: authHeader || `Bearer ${SUPABASE_ANON_KEY}`, - }; - - console.log("[create-user] Chamando Supabase com headers:", { - hasAuthHeader: !!authHeader, - hasApikey: !!fetchHeaders.apikey, - authType: authHeader ? "User Token" : "Anon Key", - }); - - const response = await fetch(`${SUPABASE_URL}/functions/v1/create-user`, { - method: "POST", - headers: fetchHeaders, - body: JSON.stringify(body), - }); - - const data = await response.json(); - - console.log("[create-user] Resposta do Supabase:", { - status: response.status, - data: JSON.stringify(data, null, 2), - }); - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } - - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } catch (error) { - console.error("Erro na API de create user:", error); - - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/delete-user.ts b/MEDICONNECT 2/netlify/functions/delete-user.ts deleted file mode 100644 index 972efa383..000000000 --- a/MEDICONNECT 2/netlify/functions/delete-user.ts +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Netlify Function: Delete User (Hard Delete) - * POST /delete-user - Deleta usuário permanentemente - * ⚠️ OPERAÇÃO IRREVERSÍVEL - Use com extremo cuidado! - * Requer permissão de admin ou gestor - * Usa Admin API do Supabase - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_SERVICE_ROLE_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc1NDk1NDM2OSwiZXhwIjoyMDcwNTMwMzY5fQ.Dez8PQkV8vWv7VkL_fZe-lY-Xs9P5VptNvRRnhkxoXw"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization, apikey", - "Access-Control-Allow-Methods": "POST, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - if (event.httpMethod !== "POST") { - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } - - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - - if (!authHeader) { - return { - statusCode: 401, - headers, - body: JSON.stringify({ - error: "Token de autenticação é obrigatório", - }), - }; - } - - const body = JSON.parse(event.body || "{}"); - - console.log("[delete-user] ATENÇÃO: Tentativa de hard delete:", { - userId: body.userId, - requestedBy: "via Netlify Function", - }); - - // Validação - if (!body.userId) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ - error: "userId é obrigatório", - }), - }; - } - - // TODO: Aqui deveria verificar se o usuário tem permissão de admin/gestor - // Verificando o token JWT que foi passado - - // Deletar usuário via Admin API do Supabase - const response = await fetch( - `${SUPABASE_URL}/auth/v1/admin/users/${body.userId}`, - { - method: "DELETE", - headers: { - apikey: SUPABASE_SERVICE_ROLE_KEY, - Authorization: `Bearer ${SUPABASE_SERVICE_ROLE_KEY}`, - "Content-Type": "application/json", - }, - } - ); - - if (response.ok) { - console.log("[delete-user] Usuário deletado com sucesso:", body.userId); - return { - statusCode: 200, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - success: true, - message: "Usuário deletado permanentemente", - userId: body.userId, - }), - }; - } - - const errorData = await response.json(); - console.error("[delete-user] Erro ao deletar:", errorData); - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - error: errorData.msg || errorData.message || "Erro ao deletar usuário", - details: errorData, - }), - }; - } catch (error) { - console.error("[delete-user] Erro:", error); - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/doctor-availability.ts b/MEDICONNECT 2/netlify/functions/doctor-availability.ts deleted file mode 100644 index fa48e3dd4..000000000 --- a/MEDICONNECT 2/netlify/functions/doctor-availability.ts +++ /dev/null @@ -1,217 +0,0 @@ -/** - * Netlify Function: doctor-availability - * - * Proxy para operações de disponibilidade dos médicos - * GET: Lista disponibilidades - * POST: Criar disponibilidade - * PATCH: Atualizar disponibilidade - * DELETE: Deletar disponibilidade - */ - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_API_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; -const SUPABASE_SERVICE_ROLE_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc1NDk1NDM2OSwiZXhwIjoyMDcwNTMwMzY5fQ.Dez8PQkV8vWv7VkL_fZe-lY-Xs9P5VptNvRRnhkxoXw"; - -export default async (req: Request) => { - // Permitir CORS - if (req.method === "OPTIONS") { - return new Response(null, { - status: 204, - headers: { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - }, - }); - } - - try { - const url = new URL(req.url); - const authHeader = req.headers.get("Authorization"); - - // Extrair ID do path se existir - const pathParts = url.pathname.split("/"); - const availabilityId = pathParts[pathParts.length - 1]; - - // GET: Listar disponibilidades - if (req.method === "GET") { - const select = url.searchParams.get("select") || "*"; - const doctor_id = url.searchParams.get("doctor_id"); - const active = url.searchParams.get("active"); - - const queryParams = new URLSearchParams(); - queryParams.append("select", select); - if (doctor_id) queryParams.append("doctor_id", `eq.${doctor_id}`); - if (active !== null) queryParams.append("active", `eq.${active}`); - - const supabaseUrl = `${SUPABASE_URL}/rest/v1/doctor_availability?${queryParams}`; - - const headers: HeadersInit = { - apikey: SUPABASE_API_KEY, - "Content-Type": "application/json", - }; - - if (authHeader) { - headers["Authorization"] = authHeader; - } - - const response = await fetch(supabaseUrl, { - method: "GET", - headers, - }); - - const data = await response.json(); - - return new Response(JSON.stringify(data), { - status: response.status, - headers: { - "Content-Type": "application/json", - "Access-Control-Allow-Origin": "*", - }, - }); - } - - // POST: Criar disponibilidade - if (req.method === "POST") { - const body = await req.json(); - - const supabaseUrl = `${SUPABASE_URL}/rest/v1/doctor_availability`; - - // Usa SERVICE ROLE KEY para ignorar políticas RLS - const headers: HeadersInit = { - apikey: SUPABASE_SERVICE_ROLE_KEY, - Authorization: `Bearer ${SUPABASE_SERVICE_ROLE_KEY}`, - "Content-Type": "application/json", - Prefer: "return=representation", - }; - - const response = await fetch(supabaseUrl, { - method: "POST", - headers, - body: JSON.stringify(body), - }); - - const data = await response.json(); - - return new Response(JSON.stringify(data), { - status: response.status, - headers: { - "Content-Type": "application/json", - "Access-Control-Allow-Origin": "*", - }, - }); - } - - // PATCH: Atualizar disponibilidade - if (req.method === "PATCH") { - if (!availabilityId || availabilityId === "doctor-availability") { - return new Response( - JSON.stringify({ error: "Availability ID is required" }), - { - status: 400, - headers: { - "Content-Type": "application/json", - "Access-Control-Allow-Origin": "*", - }, - } - ); - } - - const body = await req.json(); - - const supabaseUrl = `${SUPABASE_URL}/rest/v1/doctor_availability?id=eq.${availabilityId}`; - - const headers: HeadersInit = { - apikey: SUPABASE_API_KEY, - "Content-Type": "application/json", - Prefer: "return=representation", - }; - - if (authHeader) { - headers["Authorization"] = authHeader; - } - - const response = await fetch(supabaseUrl, { - method: "PATCH", - headers, - body: JSON.stringify(body), - }); - - const data = await response.json(); - const result = Array.isArray(data) && data.length > 0 ? data[0] : data; - - return new Response(JSON.stringify(result), { - status: response.status, - headers: { - "Content-Type": "application/json", - "Access-Control-Allow-Origin": "*", - }, - }); - } - - // DELETE: Deletar disponibilidade - if (req.method === "DELETE") { - if (!availabilityId || availabilityId === "doctor-availability") { - return new Response( - JSON.stringify({ error: "Availability ID is required" }), - { - status: 400, - headers: { - "Content-Type": "application/json", - "Access-Control-Allow-Origin": "*", - }, - } - ); - } - - const supabaseUrl = `${SUPABASE_URL}/rest/v1/doctor_availability?id=eq.${availabilityId}`; - - const headers: HeadersInit = { - apikey: SUPABASE_API_KEY, - "Content-Type": "application/json", - }; - - if (authHeader) { - headers["Authorization"] = authHeader; - } - - const response = await fetch(supabaseUrl, { - method: "DELETE", - headers, - }); - - return new Response(null, { - status: response.status, - headers: { - "Access-Control-Allow-Origin": "*", - }, - }); - } - - // Método não suportado - return new Response(JSON.stringify({ error: "Method not allowed" }), { - status: 405, - headers: { - "Content-Type": "application/json", - "Access-Control-Allow-Origin": "*", - }, - }); - } catch (error) { - console.error("Error in doctor-availability function:", error); - return new Response( - JSON.stringify({ - error: "Internal server error", - details: error instanceof Error ? error.message : "Unknown error", - }), - { - status: 500, - headers: { - "Content-Type": "application/json", - "Access-Control-Allow-Origin": "*", - }, - } - ); - } -}; diff --git a/MEDICONNECT 2/netlify/functions/doctor-exceptions.ts b/MEDICONNECT 2/netlify/functions/doctor-exceptions.ts deleted file mode 100644 index b5dc31541..000000000 --- a/MEDICONNECT 2/netlify/functions/doctor-exceptions.ts +++ /dev/null @@ -1,169 +0,0 @@ -/** - * Netlify Function: doctor-exceptions - * - * Proxy para operações de exceções na agenda dos médicos - * GET: Lista exceções - * POST: Criar exceção - * DELETE: Deletar exceção - */ - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_API_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export default async (req: Request) => { - // Permitir CORS - if (req.method === "OPTIONS") { - return new Response(null, { - status: 204, - headers: { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - }, - }); - } - - try { - const url = new URL(req.url); - const authHeader = req.headers.get("Authorization"); - - // Extrair ID do path se existir - const pathParts = url.pathname.split("/"); - const exceptionId = pathParts[pathParts.length - 1]; - - // GET: Listar exceções - if (req.method === "GET") { - const select = url.searchParams.get("select") || "*"; - const doctor_id = url.searchParams.get("doctor_id"); - const date = url.searchParams.get("date"); - - const queryParams = new URLSearchParams(); - queryParams.append("select", select); - if (doctor_id) queryParams.append("doctor_id", `eq.${doctor_id}`); - if (date) queryParams.append("date", `eq.${date}`); - - const supabaseUrl = `${SUPABASE_URL}/rest/v1/doctor_exceptions?${queryParams}`; - - const headers: HeadersInit = { - apikey: SUPABASE_API_KEY, - "Content-Type": "application/json", - }; - - if (authHeader) { - headers["Authorization"] = authHeader; - } - - const response = await fetch(supabaseUrl, { - method: "GET", - headers, - }); - - const data = await response.json(); - - return new Response(JSON.stringify(data), { - status: response.status, - headers: { - "Content-Type": "application/json", - "Access-Control-Allow-Origin": "*", - }, - }); - } - - // POST: Criar exceção - if (req.method === "POST") { - const body = await req.json(); - - const supabaseUrl = `${SUPABASE_URL}/rest/v1/doctor_exceptions`; - - const headers: HeadersInit = { - apikey: SUPABASE_API_KEY, - "Content-Type": "application/json", - Prefer: "return=representation", - }; - - if (authHeader) { - headers["Authorization"] = authHeader; - } - - const response = await fetch(supabaseUrl, { - method: "POST", - headers, - body: JSON.stringify(body), - }); - - const data = await response.json(); - - return new Response(JSON.stringify(data), { - status: response.status, - headers: { - "Content-Type": "application/json", - "Access-Control-Allow-Origin": "*", - }, - }); - } - - // DELETE: Deletar exceção - if (req.method === "DELETE") { - if (!exceptionId || exceptionId === "doctor-exceptions") { - return new Response( - JSON.stringify({ error: "Exception ID is required" }), - { - status: 400, - headers: { - "Content-Type": "application/json", - "Access-Control-Allow-Origin": "*", - }, - } - ); - } - - const supabaseUrl = `${SUPABASE_URL}/rest/v1/doctor_exceptions?id=eq.${exceptionId}`; - - const headers: HeadersInit = { - apikey: SUPABASE_API_KEY, - "Content-Type": "application/json", - }; - - if (authHeader) { - headers["Authorization"] = authHeader; - } - - const response = await fetch(supabaseUrl, { - method: "DELETE", - headers, - }); - - return new Response(null, { - status: response.status, - headers: { - "Access-Control-Allow-Origin": "*", - }, - }); - } - - // Método não suportado - return new Response(JSON.stringify({ error: "Method not allowed" }), { - status: 405, - headers: { - "Content-Type": "application/json", - "Access-Control-Allow-Origin": "*", - }, - }); - } catch (error) { - console.error("Error in doctor-exceptions function:", error); - return new Response( - JSON.stringify({ - error: "Internal server error", - details: error instanceof Error ? error.message : "Unknown error", - }), - { - status: 500, - headers: { - "Content-Type": "application/json", - "Access-Control-Allow-Origin": "*", - }, - } - ); - } -}; diff --git a/MEDICONNECT 2/netlify/functions/doctors.ts b/MEDICONNECT 2/netlify/functions/doctors.ts deleted file mode 100644 index 3527f18c3..000000000 --- a/MEDICONNECT 2/netlify/functions/doctors.ts +++ /dev/null @@ -1,237 +0,0 @@ -/** - * Netlify Function: Doctors CRUD - * GET /rest/v1/doctors - Lista médicos - * GET /rest/v1/doctors/{id} - Busca por ID - * POST /rest/v1/doctors - Cria médico - * PATCH /rest/v1/doctors/{id} - Atualiza médico - * DELETE /rest/v1/doctors/{id} - Deleta médico - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - - if (!authHeader) { - return { - statusCode: 401, - headers, - body: JSON.stringify({ error: "Token não fornecido" }), - }; - } - - // Extrai ID da URL se houver (doctors/123 ou doctors?id=123) - const pathParts = event.path.split("/"); - const doctorId = - pathParts[pathParts.length - 1] !== "doctors" - ? pathParts[pathParts.length - 1] - : null; - - // GET - Listar ou buscar por ID - if (event.httpMethod === "GET") { - let url = `${SUPABASE_URL}/rest/v1/doctors`; - - if (doctorId && doctorId !== "doctors") { - // Buscar por ID específico - url += `?id=eq.${doctorId}&select=*`; - } else if (event.queryStringParameters) { - // Adiciona filtros da query string - const params = new URLSearchParams( - event.queryStringParameters as Record - ); - url += `?${params.toString()}`; - - // Adiciona select=* se não tiver - if (!params.has("select")) { - url += url.includes("?") ? "&select=*" : "?select=*"; - } - } else { - url += "?select=*"; - } - - const response = await fetch(url, { - method: "GET", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - }, - }); - - let data = await response.json(); - - // Se buscar por ID, retorna o objeto diretamente (não array) - if ( - doctorId && - doctorId !== "doctors" && - Array.isArray(data) && - data.length > 0 - ) { - data = data[0]; - } - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } - - // POST - Criar médico - if (event.httpMethod === "POST") { - const body = JSON.parse(event.body || "{}"); - - if ( - !body.crm || - !body.crm_uf || - !body.full_name || - !body.cpf || - !body.email - ) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ - error: "Campos obrigatórios: crm, crm_uf, full_name, cpf, email", - }), - }; - } - - const response = await fetch(`${SUPABASE_URL}/rest/v1/doctors`, { - method: "POST", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - Prefer: "return=representation", - }, - body: JSON.stringify(body), - }); - - let data = await response.json(); - - // Supabase retorna array, pega o primeiro - if (Array.isArray(data) && data.length > 0) { - data = data[0]; - } - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } - - // PATCH - Atualizar médico - if (event.httpMethod === "PATCH") { - if (!doctorId || doctorId === "doctors") { - return { - statusCode: 400, - headers, - body: JSON.stringify({ error: "ID do médico é obrigatório" }), - }; - } - - const body = JSON.parse(event.body || "{}"); - - const response = await fetch( - `${SUPABASE_URL}/rest/v1/doctors?id=eq.${doctorId}`, - { - method: "PATCH", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - Prefer: "return=representation", - }, - body: JSON.stringify(body), - } - ); - - let data = await response.json(); - - if (Array.isArray(data) && data.length > 0) { - data = data[0]; - } - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } - - // DELETE - Deletar médico - if (event.httpMethod === "DELETE") { - if (!doctorId || doctorId === "doctors") { - return { - statusCode: 400, - headers, - body: JSON.stringify({ error: "ID do médico é obrigatório" }), - }; - } - - const response = await fetch( - `${SUPABASE_URL}/rest/v1/doctors?id=eq.${doctorId}`, - { - method: "DELETE", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - }, - } - ); - - return { - statusCode: response.status, - headers, - body: "", - }; - } - - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } catch (error) { - console.error("Erro na API de médicos:", error); - - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/get-available-slots.ts b/MEDICONNECT 2/netlify/functions/get-available-slots.ts deleted file mode 100644 index 4cbba71eb..000000000 --- a/MEDICONNECT 2/netlify/functions/get-available-slots.ts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Netlify Function: Get Available Slots - * POST /functions/v1/get-available-slots - Busca horários disponíveis - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "POST, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - - if (!authHeader) { - return { - statusCode: 401, - headers, - body: JSON.stringify({ error: "Token não fornecido" }), - }; - } - - if (event.httpMethod === "POST") { - const body = JSON.parse(event.body || "{}"); - - // Validação dos campos obrigatórios - if (!body.doctor_id || !body.start_date || !body.end_date) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ - error: "Campos obrigatórios: doctor_id, start_date, end_date", - }), - }; - } - - const response = await fetch( - `${SUPABASE_URL}/functions/v1/get-available-slots`, - { - method: "POST", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - }, - body: JSON.stringify(body), - } - ); - - const data = await response.json(); - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } - - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } catch (error) { - console.error("Erro na API de available slots:", error); - - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/patients.ts b/MEDICONNECT 2/netlify/functions/patients.ts deleted file mode 100644 index 73bb18a08..000000000 --- a/MEDICONNECT 2/netlify/functions/patients.ts +++ /dev/null @@ -1,226 +0,0 @@ -/** - * Netlify Function: Patients CRUD - * GET /rest/v1/patients - Lista pacientes - * GET /rest/v1/patients/{id} - Busca por ID - * POST /rest/v1/patients - Cria paciente - * PATCH /rest/v1/patients/{id} - Atualiza paciente - * DELETE /rest/v1/patients/{id} - Deleta paciente - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - - if (!authHeader) { - return { - statusCode: 401, - headers, - body: JSON.stringify({ error: "Token não fornecido" }), - }; - } - - // Extrai ID da URL se houver - const pathParts = event.path.split("/"); - const patientId = - pathParts[pathParts.length - 1] !== "patients" - ? pathParts[pathParts.length - 1] - : null; - - // GET - Listar ou buscar por ID - if (event.httpMethod === "GET") { - let url = `${SUPABASE_URL}/rest/v1/patients`; - - if (patientId && patientId !== "patients") { - url += `?id=eq.${patientId}&select=*`; - } else if (event.queryStringParameters) { - const params = new URLSearchParams( - event.queryStringParameters as Record - ); - url += `?${params.toString()}`; - - if (!params.has("select")) { - url += url.includes("?") ? "&select=*" : "?select=*"; - } - } else { - url += "?select=*"; - } - - const response = await fetch(url, { - method: "GET", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - }, - }); - - let data = await response.json(); - - if ( - patientId && - patientId !== "patients" && - Array.isArray(data) && - data.length > 0 - ) { - data = data[0]; - } - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } - - // POST - Criar paciente - if (event.httpMethod === "POST") { - const body = JSON.parse(event.body || "{}"); - - if (!body.full_name || !body.cpf || !body.email || !body.phone_mobile) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ - error: "Campos obrigatórios: full_name, cpf, email, phone_mobile", - }), - }; - } - - const response = await fetch(`${SUPABASE_URL}/rest/v1/patients`, { - method: "POST", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - Prefer: "return=representation", - }, - body: JSON.stringify(body), - }); - - let data = await response.json(); - - if (Array.isArray(data) && data.length > 0) { - data = data[0]; - } - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } - - // PATCH - Atualizar paciente - if (event.httpMethod === "PATCH") { - if (!patientId || patientId === "patients") { - return { - statusCode: 400, - headers, - body: JSON.stringify({ error: "ID do paciente é obrigatório" }), - }; - } - - const body = JSON.parse(event.body || "{}"); - - const response = await fetch( - `${SUPABASE_URL}/rest/v1/patients?id=eq.${patientId}`, - { - method: "PATCH", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - Prefer: "return=representation", - }, - body: JSON.stringify(body), - } - ); - - let data = await response.json(); - - if (Array.isArray(data) && data.length > 0) { - data = data[0]; - } - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } - - // DELETE - Deletar paciente - if (event.httpMethod === "DELETE") { - if (!patientId || patientId === "patients") { - return { - statusCode: 400, - headers, - body: JSON.stringify({ error: "ID do paciente é obrigatório" }), - }; - } - - const response = await fetch( - `${SUPABASE_URL}/rest/v1/patients?id=eq.${patientId}`, - { - method: "DELETE", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - }, - } - ); - - return { - statusCode: response.status, - headers, - body: "", - }; - } - - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } catch (error) { - console.error("Erro na API de pacientes:", error); - - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/profiles.ts b/MEDICONNECT 2/netlify/functions/profiles.ts deleted file mode 100644 index 4c8c02d06..000000000 --- a/MEDICONNECT 2/netlify/functions/profiles.ts +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Netlify Function: Profiles - * GET /rest/v1/profiles - Lista perfis - * GET /rest/v1/profiles/{id} - Busca por ID - * PATCH /rest/v1/profiles/{id} - Atualiza avatar_url - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "GET, PATCH, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - - if (!authHeader) { - return { - statusCode: 401, - headers, - body: JSON.stringify({ error: "Token não fornecido" }), - }; - } - - // Extrai ID da URL se houver - const pathParts = event.path.split("/"); - const profileId = - pathParts[pathParts.length - 1] !== "profiles" - ? pathParts[pathParts.length - 1] - : null; - - // GET - Listar ou buscar por ID - if (event.httpMethod === "GET") { - let url = `${SUPABASE_URL}/rest/v1/profiles`; - - if (profileId && profileId !== "profiles") { - url += `?id=eq.${profileId}&select=*`; - } else if (event.queryStringParameters) { - const params = new URLSearchParams( - event.queryStringParameters as Record - ); - url += `?${params.toString()}`; - - if (!params.has("select")) { - url += url.includes("?") ? "&select=*" : "?select=*"; - } - } else { - url += "?select=*"; - } - - const response = await fetch(url, { - method: "GET", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - }, - }); - - let data = await response.json(); - - if ( - profileId && - profileId !== "profiles" && - Array.isArray(data) && - data.length > 0 - ) { - data = data[0]; - } - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } - - // PATCH - Atualizar avatar_url - if (event.httpMethod === "PATCH") { - if (!profileId || profileId === "profiles") { - return { - statusCode: 400, - headers, - body: JSON.stringify({ error: "ID do perfil é obrigatório" }), - }; - } - - const body = JSON.parse(event.body || "{}"); - - const response = await fetch( - `${SUPABASE_URL}/rest/v1/profiles?id=eq.${profileId}`, - { - method: "PATCH", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - Prefer: "return=representation", - }, - body: JSON.stringify(body), - } - ); - - let data = await response.json(); - - if (Array.isArray(data) && data.length > 0) { - data = data[0]; - } - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } - - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } catch (error) { - console.error("Erro na API de perfis:", error); - - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/register-patient.ts b/MEDICONNECT 2/netlify/functions/register-patient.ts deleted file mode 100644 index 304c8f196..000000000 --- a/MEDICONNECT 2/netlify/functions/register-patient.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Netlify Function: Register Patient (Public) - * POST /register-patient - Registro público de paciente - * Não requer autenticação - função pública - * Validações rigorosas (CPF, rate limiting, rollback) - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "POST, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - try { - if (event.httpMethod === "POST") { - const body = JSON.parse(event.body || "{}"); - - console.log( - "[register-patient] Recebido body:", - JSON.stringify(body, null, 2) - ); - - // Validação dos campos obrigatórios - if (!body.email || !body.full_name || !body.cpf || !body.phone_mobile) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ - error: "Campos obrigatórios: email, full_name, cpf, phone_mobile", - }), - }; - } - - // Chama a Edge Function pública do Supabase - const response = await fetch( - `${SUPABASE_URL}/functions/v1/register-patient`, - { - method: "POST", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: `Bearer ${SUPABASE_ANON_KEY}`, - "Content-Type": "application/json", - }, - body: JSON.stringify(body), - } - ); - - const data = await response.json(); - - console.log("[register-patient] Resposta do Supabase:", { - status: response.status, - data: JSON.stringify(data, null, 2), - }); - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } - - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } catch (error) { - console.error("[register-patient] Erro na API:", error); - - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno do servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/reports.ts b/MEDICONNECT 2/netlify/functions/reports.ts deleted file mode 100644 index 23323f5d8..000000000 --- a/MEDICONNECT 2/netlify/functions/reports.ts +++ /dev/null @@ -1,197 +0,0 @@ -/** - * Netlify Function: Reports - * GET /rest/v1/reports - Lista relatórios - * GET /rest/v1/reports/{id} - Busca por ID - * POST /rest/v1/reports - Cria relatório - * PATCH /rest/v1/reports/{id} - Atualiza relatório - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "GET, POST, PATCH, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - - if (!authHeader) { - return { - statusCode: 401, - headers, - body: JSON.stringify({ error: "Token não fornecido" }), - }; - } - - // Extrai ID da URL se houver - const pathParts = event.path.split("/"); - const reportId = - pathParts[pathParts.length - 1] !== "reports" - ? pathParts[pathParts.length - 1] - : null; - - // GET - Listar ou buscar por ID - if (event.httpMethod === "GET") { - let url = `${SUPABASE_URL}/rest/v1/reports`; - - if (reportId && reportId !== "reports") { - url += `?id=eq.${reportId}&select=*`; - } else if (event.queryStringParameters) { - const params = new URLSearchParams( - event.queryStringParameters as Record - ); - url += `?${params.toString()}`; - - if (!params.has("select")) { - url += url.includes("?") ? "&select=*" : "?select=*"; - } - } else { - url += "?select=*"; - } - - const response = await fetch(url, { - method: "GET", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - }, - }); - - let data = await response.json(); - - if ( - reportId && - reportId !== "reports" && - Array.isArray(data) && - data.length > 0 - ) { - data = data[0]; - } - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } - - // POST - Criar relatório - if (event.httpMethod === "POST") { - const body = JSON.parse(event.body || "{}"); - - if (!body.patient_id) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ - error: "Campo obrigatório: patient_id", - }), - }; - } - - const response = await fetch(`${SUPABASE_URL}/rest/v1/reports`, { - method: "POST", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - Prefer: "return=representation", - }, - body: JSON.stringify(body), - }); - - let data = await response.json(); - - if (Array.isArray(data) && data.length > 0) { - data = data[0]; - } - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } - - // PATCH - Atualizar relatório - if (event.httpMethod === "PATCH") { - if (!reportId || reportId === "reports") { - return { - statusCode: 400, - headers, - body: JSON.stringify({ error: "ID do relatório é obrigatório" }), - }; - } - - const body = JSON.parse(event.body || "{}"); - - const response = await fetch( - `${SUPABASE_URL}/rest/v1/reports?id=eq.${reportId}`, - { - method: "PATCH", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - Prefer: "return=representation", - }, - body: JSON.stringify(body), - } - ); - - let data = await response.json(); - - if (Array.isArray(data) && data.length > 0) { - data = data[0]; - } - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } - - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } catch (error) { - console.error("Erro na API de relatórios:", error); - - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/request-password-reset.ts b/MEDICONNECT 2/netlify/functions/request-password-reset.ts deleted file mode 100644 index b100d2725..000000000 --- a/MEDICONNECT 2/netlify/functions/request-password-reset.ts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Netlify Function: Request Password Reset - * POST /request-password-reset - Solicita reset de senha via email (público) - * Não requer autenticação - endpoint público - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -interface PasswordResetRequest { - email: string; - redirect_url?: string; -} - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "POST, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - if (event.httpMethod !== "POST") { - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } - - try { - const body: PasswordResetRequest = JSON.parse(event.body || "{}"); - - console.log("[request-password-reset] Recebido:", { - email: body.email, - hasRedirectUrl: !!body.redirect_url, - }); - - if (!body.email) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ error: "Email é obrigatório" }), - }; - } - - // Chama a API do Supabase para enviar email de reset - const response = await fetch(`${SUPABASE_URL}/auth/v1/recover`, { - method: "POST", - headers: { - "Content-Type": "application/json", - apikey: SUPABASE_ANON_KEY, - }, - body: JSON.stringify({ - email: body.email, - options: { - redirectTo: - body.redirect_url || - "https://mediconnectbrasil.netlify.app/reset-password", - }, - }), - }); - - const data = await response.json(); - - console.log("[request-password-reset] Resposta Supabase:", { - status: response.status, - data, - }); - - // Supabase sempre retorna 200 mesmo se o email não existir (por segurança) - if (response.ok) { - return { - statusCode: 200, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - success: true, - message: - "Email de reset de senha enviado com sucesso. Verifique sua caixa de entrada.", - }), - }; - } - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } catch (error) { - console.error("[request-password-reset] Erro:", error); - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro ao solicitar reset de senha", - details: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/send-sms.ts b/MEDICONNECT 2/netlify/functions/send-sms.ts deleted file mode 100644 index 57e76a4ab..000000000 --- a/MEDICONNECT 2/netlify/functions/send-sms.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Netlify Function: Send SMS - * POST /functions/v1/send-sms - Envia SMS via Twilio - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "POST, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - - if (!authHeader) { - return { - statusCode: 401, - headers, - body: JSON.stringify({ error: "Token não fornecido" }), - }; - } - - if (event.httpMethod === "POST") { - const body = JSON.parse(event.body || "{}"); - - // Validação dos campos obrigatórios - if (!body.phone_number || !body.message) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ - error: "Campos obrigatórios: phone_number, message", - }), - }; - } - - // Chama a função Supabase de enviar SMS - const response = await fetch(`${SUPABASE_URL}/functions/v1/send-sms`, { - method: "POST", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - }, - body: JSON.stringify(body), - }); - - const data = await response.json(); - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } - - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } catch (error) { - console.error("Erro na API de SMS:", error); - - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/user-info-by-id.ts b/MEDICONNECT 2/netlify/functions/user-info-by-id.ts deleted file mode 100644 index 4f3a4e2b5..000000000 --- a/MEDICONNECT 2/netlify/functions/user-info-by-id.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Netlify Function: User Info By ID - * POST /user-info-by-id - Retorna dados de usuário específico (apenas admin/gestor) - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "POST, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - if (event.httpMethod !== "POST") { - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } - - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - - if (!authHeader) { - return { - statusCode: 401, - headers, - body: JSON.stringify({ error: "Token não fornecido" }), - }; - } - - const body = JSON.parse(event.body || "{}"); - - if (!body.user_id) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ error: "Campo obrigatório: user_id" }), - }; - } - - // Chama a Edge Function do Supabase - const response = await fetch( - `${SUPABASE_URL}/functions/v1/user-info-by-id`, - { - method: "POST", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - }, - body: JSON.stringify(body), - } - ); - - const data = await response.json(); - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } catch (error) { - console.error("Erro na API de user-info-by-id:", error); - - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/user-info.ts b/MEDICONNECT 2/netlify/functions/user-info.ts deleted file mode 100644 index e197758fe..000000000 --- a/MEDICONNECT 2/netlify/functions/user-info.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Netlify Function: User Info - * GET /functions/v1/user-info - Retorna informações completas do usuário autenticado - * Inclui: user, profile, roles e permissions calculadas - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "POST, GET, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - - if (!authHeader) { - return { - statusCode: 401, - headers, - body: JSON.stringify({ error: "Token não fornecido" }), - }; - } - - // Aceita tanto POST quanto GET para compatibilidade - if (event.httpMethod === "POST" || event.httpMethod === "GET") { - // Chama a Edge Function do Supabase (POST conforme doc 21/10/2025) - const response = await fetch(`${SUPABASE_URL}/functions/v1/user-info`, { - method: "POST", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - }, - }); - - const data = await response.json(); - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } - - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } catch (error) { - console.error("Erro na API de user-info:", error); - - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/netlify/functions/user-roles.ts b/MEDICONNECT 2/netlify/functions/user-roles.ts deleted file mode 100644 index b05b6f5a7..000000000 --- a/MEDICONNECT 2/netlify/functions/user-roles.ts +++ /dev/null @@ -1,161 +0,0 @@ -/** - * Netlify Function: User Roles - * GET /rest/v1/user_roles - Lista roles de usuários - * POST /rest/v1/user_roles - Adiciona role a um usuário - * DELETE /rest/v1/user_roles - Remove role de um usuário - */ - -import type { Handler, HandlerEvent } from "@netlify/functions"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -export const handler: Handler = async (event: HandlerEvent) => { - const headers = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS", - }; - - if (event.httpMethod === "OPTIONS") { - return { - statusCode: 200, - headers, - body: "", - }; - } - - try { - const authHeader = - event.headers.authorization || event.headers.Authorization; - - if (!authHeader) { - return { - statusCode: 401, - headers, - body: JSON.stringify({ error: "Token não fornecido" }), - }; - } - - if (event.httpMethod === "GET") { - let url = `${SUPABASE_URL}/rest/v1/user_roles?select=*`; - - if (event.queryStringParameters) { - const params = new URLSearchParams( - event.queryStringParameters as Record - ); - const paramsStr = params.toString(); - if (paramsStr) { - url += `&${paramsStr}`; - } - } - - const response = await fetch(url, { - method: "GET", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - }, - }); - - const data = await response.json(); - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } - - if (event.httpMethod === "POST") { - // Adicionar nova role para um usuário - const body = JSON.parse(event.body || "{}"); - - if (!body.user_id || !body.role) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ error: "user_id e role são obrigatórios" }), - }; - } - - const response = await fetch(`${SUPABASE_URL}/rest/v1/user_roles`, { - method: "POST", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - Prefer: "return=representation", - }, - body: JSON.stringify({ - user_id: body.user_id, - role: body.role, - }), - }); - - const data = await response.json(); - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }; - } - - if (event.httpMethod === "DELETE") { - // Remover role de um usuário - const params = event.queryStringParameters; - - if (!params?.user_id || !params?.role) { - return { - statusCode: 400, - headers, - body: JSON.stringify({ error: "user_id e role são obrigatórios" }), - }; - } - - const url = `${SUPABASE_URL}/rest/v1/user_roles?user_id=eq.${params.user_id}&role=eq.${params.role}`; - - const response = await fetch(url, { - method: "DELETE", - headers: { - apikey: SUPABASE_ANON_KEY, - Authorization: authHeader, - }, - }); - - return { - statusCode: response.status, - headers: { - ...headers, - "Content-Type": "application/json", - }, - body: JSON.stringify({ success: true }), - }; - } - - return { - statusCode: 405, - headers, - body: JSON.stringify({ error: "Method Not Allowed" }), - }; - } catch (error) { - console.error("Erro na API de user roles:", error); - - return { - statusCode: 500, - headers, - body: JSON.stringify({ - error: "Erro interno no servidor", - message: error instanceof Error ? error.message : "Erro desconhecido", - }), - }; - } -}; diff --git a/MEDICONNECT 2/public/_redirects b/MEDICONNECT 2/public/_redirects deleted file mode 100644 index 430850547..000000000 --- a/MEDICONNECT 2/public/_redirects +++ /dev/null @@ -1 +0,0 @@ -/* /index.html 200 diff --git a/MEDICONNECT 2/public/logo.PNG b/MEDICONNECT 2/public/logo.PNG new file mode 100644 index 000000000..4e4a03fb6 Binary files /dev/null and b/MEDICONNECT 2/public/logo.PNG differ diff --git a/MEDICONNECT 2/scripts/cleanup-users.js b/MEDICONNECT 2/scripts/cleanup-users.js new file mode 100644 index 000000000..0c9290714 --- /dev/null +++ b/MEDICONNECT 2/scripts/cleanup-users.js @@ -0,0 +1,134 @@ +import axios from "axios"; + +const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; +const ANON_KEY = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; + +let ACCESS_TOKEN = ""; + +// 1. Login como admin +async function login() { + console.log("\n🔐 Fazendo login como admin..."); + try { + const response = await axios.post( + `${SUPABASE_URL}/auth/v1/token?grant_type=password`, + { + email: "riseup@popcode.com.br", + password: "riseup", + }, + { + headers: { + "Content-Type": "application/json", + apikey: ANON_KEY, + }, + } + ); + + ACCESS_TOKEN = response.data.access_token; + console.log("✅ Login realizado com sucesso!"); + console.log(`📧 Email: ${response.data.user.email}`); + console.log(`🆔 User ID: ${response.data.user.id}`); + return response.data; + } catch (error) { + console.error("❌ Erro no login:", error.response?.data || error.message); + process.exit(1); + } +} + +// 2. Listar usuários +async function listUsers() { + console.log("\n📋 Listando usuários...\n"); + try { + const response = await axios.get( + `${SUPABASE_URL}/rest/v1/profiles?select=id,full_name,email`, + { + headers: { + apikey: ANON_KEY, + Authorization: `Bearer ${ACCESS_TOKEN}`, + }, + } + ); + + console.log(`✅ ${response.data.length} usuários encontrados:\n`); + + response.data.forEach((user, index) => { + console.log(`${index + 1}. ${user.full_name || "Sem nome"}`); + console.log(` 📧 Email: ${user.email}`); + console.log(` 🆔 ID: ${user.id}\n`); + }); + + return response.data; + } catch (error) { + console.error( + "❌ Erro ao listar usuários:", + error.response?.data || error.message + ); + return []; + } +} + +// 3. Deletar usuário +async function deleteUser(userId, userName) { + console.log(`\n🗑️ Deletando usuário: ${userName} (${userId})...`); + try { + const response = await axios.post( + `${SUPABASE_URL}/functions/v1/delete-user`, + { userId }, + { + headers: { + "Content-Type": "application/json", + apikey: ANON_KEY, + Authorization: `Bearer ${ACCESS_TOKEN}`, + }, + } + ); + + console.log(`✅ ${userName} deletado com sucesso!`); + return response.data; + } catch (error) { + console.error( + `❌ Erro ao deletar ${userName}:`, + error.response?.data || error.message + ); + } +} + +// Script principal +async function main() { + console.log("🧹 Iniciando limpeza de usuários de teste..."); + + // 1. Login + await login(); + + // 2. Listar usuários atuais + const users = await listUsers(); + + // 3. Lista de emails para deletar (apenas os que o assistente criou) + const testEmails = [ + "admin@mediconnect.com", + "secretaria@mediconnect.com", + "dr.medico@mediconnect.com", + "fernando.pirichowski@souunit.com.br", + ]; + + // 4. Deletar usuários de teste + let deletedCount = 0; + for (const user of users) { + if (testEmails.includes(user.email)) { + await deleteUser(user.id, user.full_name || user.email); + deletedCount++; + // Aguardar 1 segundo entre deleções + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + } + + console.log( + `\n✅ Limpeza concluída! ${deletedCount} usuários de teste deletados.` + ); + + // 5. Listar usuários finais + console.log("\n📊 Usuários restantes:"); + await listUsers(); +} + +main().catch(console.error); diff --git a/MEDICONNECT 2/scripts/manage-users.js b/MEDICONNECT 2/scripts/manage-users.js new file mode 100644 index 000000000..2c934b5e1 --- /dev/null +++ b/MEDICONNECT 2/scripts/manage-users.js @@ -0,0 +1,275 @@ +import axios from "axios"; + +const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; +const ANON_KEY = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; + +// Credenciais do admin +const ADMIN_EMAIL = "riseup@popcode.com.br"; +const ADMIN_PASSWORD = "riseup"; + +let ACCESS_TOKEN = ""; + +// 1. Fazer login como admin +async function login() { + console.log("\n🔐 Fazendo login como admin..."); + try { + const response = await axios.post( + `${SUPABASE_URL}/auth/v1/token?grant_type=password`, + { + email: ADMIN_EMAIL, + password: ADMIN_PASSWORD, + }, + { + headers: { + "Content-Type": "application/json", + apikey: ANON_KEY, + }, + } + ); + + ACCESS_TOKEN = response.data.access_token; + console.log("✅ Login realizado com sucesso!"); + console.log("📧 Email:", response.data.user.email); + console.log("🆔 User ID:", response.data.user.id); + return response.data; + } catch (error) { + console.error("❌ Erro no login:", error.response?.data || error.message); + process.exit(1); + } +} + +// 2. Listar todos os usuários (via profiles - simplificado) +async function listUsers() { + console.log("\n📋 Listando usuários..."); + try { + const response = await axios.get( + `${SUPABASE_URL}/rest/v1/profiles?select=id,full_name,email`, + { + headers: { + apikey: ANON_KEY, + Authorization: `Bearer ${ACCESS_TOKEN}`, + }, + } + ); + + console.log(`\n✅ ${response.data.length} usuários encontrados:\n`); + response.data.forEach((user, index) => { + console.log(`${index + 1}. ${user.full_name || "Sem nome"}`); + console.log(` 📧 Email: ${user.email || "Sem email"}`); + console.log(` 🆔 ID: ${user.id}`); + console.log(""); + }); + + return response.data; + } catch (error) { + console.error( + "❌ Erro ao listar usuários:", + error.response?.data || error.message + ); + return []; + } +} + +// 3. Deletar usuário (Edge Function) +async function deleteUser(userId, userName) { + console.log(`\n🗑️ Deletando usuário: ${userName} (${userId})...`); + try { + const response = await axios.post( + `${SUPABASE_URL}/functions/v1/delete-user`, + { userId }, + { + headers: { + "Content-Type": "application/json", + apikey: ANON_KEY, + Authorization: `Bearer ${ACCESS_TOKEN}`, + }, + } + ); + + console.log(`✅ ${userName} deletado com sucesso!`); + return response.data; + } catch (error) { + console.error( + `❌ Erro ao deletar ${userName}:`, + error.response?.data || error.message + ); + } +} + +// 4. Criar novo usuário com Edge Function +async function createUserWithPassword(email, password, fullName, role) { + console.log(`\n➕ Criando usuário: ${fullName} (${role})...`); + try { + const response = await axios.post( + `${SUPABASE_URL}/functions/v1/create-user`, + { + email, + password, + full_name: fullName, + role, + }, + { + headers: { + "Content-Type": "application/json", + apikey: ANON_KEY, + Authorization: `Bearer ${ACCESS_TOKEN}`, + }, + } + ); + + console.log(`✅ ${fullName} criado com sucesso!`); + console.log(` 📧 Email: ${email}`); + console.log(` 🔑 Senha: ${password}`); + console.log(` 👤 Role: ${role}`); + return response.data; + } catch (error) { + console.error( + `❌ Erro ao criar ${fullName}:`, + error.response?.data || error.message + ); + } +} + +// 5. Criar médico com Edge Function +async function createDoctor( + email, + password, + fullName, + especialidade, + crm, + crmUf, + cpf +) { + console.log(`\n➕ Criando médico: ${fullName}...`); + try { + const response = await axios.post( + `${SUPABASE_URL}/functions/v1/create-doctor`, + { + email, + password, + full_name: fullName, + cpf, + especialidade, + crm, + crm_uf: crmUf, + }, + { + headers: { + "Content-Type": "application/json", + apikey: ANON_KEY, + Authorization: `Bearer ${ACCESS_TOKEN}`, + }, + } + ); + + console.log(`✅ ${fullName} criado com sucesso!`); + console.log(` 📧 Email: ${email}`); + console.log(` 🔑 Senha: ${password}`); + console.log(` 🆔 CPF: ${cpf}`); + console.log(` 🩺 Especialidade: ${especialidade}`); + console.log(` 📋 CRM: ${crm}-${crmUf}`); + return response.data; + } catch (error) { + console.error( + `❌ Erro ao criar ${fullName}:`, + error.response?.data || error.message + ); + } +} + +// Script principal +async function main() { + console.log("🚀 Iniciando gerenciamento de usuários..."); + + // 1. Login + await login(); + + // 2. Listar usuários atuais + const users = await listUsers(); + + // 3. Encontrar e deletar admin e médico específicos (por email) + const adminToDelete = users.find((u) => u.email === "admin@mediconnect.com"); + + const secretariaToDelete = users.find( + (u) => u.email === "secretaria@mediconnect.com" + ); + + const medicoToDelete = users.find( + (u) => + u.email === "medico@mediconnect.com" || + u.email === "dr.medico@mediconnect.com" + ); + + if (adminToDelete) { + await deleteUser( + adminToDelete.id, + adminToDelete.full_name || adminToDelete.email + ); + } else { + console.log("\n⚠️ Nenhum admin adicional encontrado para deletar"); + } + + if (secretariaToDelete) { + await deleteUser( + secretariaToDelete.id, + secretariaToDelete.full_name || secretariaToDelete.email + ); + } else { + console.log("\n⚠️ Nenhuma secretária encontrada para deletar"); + } + + if (medicoToDelete) { + await deleteUser( + medicoToDelete.id, + medicoToDelete.full_name || medicoToDelete.email + ); + } else { + console.log("\n⚠️ Nenhum médico encontrado para deletar"); + } + + // 4. Aguardar um pouco + console.log("\n⏳ Aguardando 2 segundos..."); + await new Promise((resolve) => setTimeout(resolve, 2000)); + + // 5. Criar novos usuários + await createUserWithPassword( + "admin@mediconnect.com", + "admin123", + "Administrador Sistema", + "admin" + ); + + await createUserWithPassword( + "secretaria@mediconnect.com", + "secretaria123", + "Secretária Sistema", + "secretaria" + ); + + await createDoctor( + "dr.medico@mediconnect.com", + "medico123", + "Dr. João Silva", + "Cardiologia", + "12345", + "SP", + "12345678900" + ); + + // 6. Listar usuários finais + console.log("\n📊 Estado final dos usuários:"); + await listUsers(); + + console.log("\n✅ Processo concluído!"); + console.log("\n📝 Credenciais dos novos usuários:"); + console.log(" 👨‍💼 Admin: admin@mediconnect.com / admin123"); + console.log(" �‍💼 Secretária: secretaria@mediconnect.com / secretaria123"); + console.log(" �👨‍⚕️ Médico: dr.medico@mediconnect.com / medico123"); + console.log(" 🆔 CPF: 12345678900"); + console.log(" 🩺 Especialidade: Cardiologia"); + console.log(" 📋 CRM: 12345-SP"); +} + +// Executar +main().catch(console.error); diff --git a/MEDICONNECT 2/search-fernando.cjs b/MEDICONNECT 2/search-fernando.cjs new file mode 100644 index 000000000..11067f49b --- /dev/null +++ b/MEDICONNECT 2/search-fernando.cjs @@ -0,0 +1,86 @@ +const axios = require('axios'); + +const ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ'; +const BASE_URL = 'https://yuanqfswhberkoevtmfr.supabase.co'; + +(async () => { + try { + console.log('🔐 Fazendo login como admin...'); + const loginRes = await axios.post(`${BASE_URL}/auth/v1/token?grant_type=password`, { + email: 'riseup@popcode.com.br', + password: 'riseup' + }, { + headers: { + 'Content-Type': 'application/json', + 'apikey': ANON_KEY + } + }); + + console.log('✅ Login admin bem-sucedido!\n'); + const token = loginRes.data.access_token; + + console.log('🔍 Buscando usuário fernando na tabela profiles...'); + const profilesRes = await axios.get(`${BASE_URL}/rest/v1/profiles?select=*`, { + headers: { + 'apikey': ANON_KEY, + 'Authorization': `Bearer ${token}` + } + }); + + console.log(`📊 Total de profiles: ${profilesRes.data.length}\n`); + + const fernandoProfile = profilesRes.data.find(u => + u.email && ( + u.email.toLowerCase().includes('fernando') || + u.full_name?.toLowerCase().includes('fernando') + ) + ); + + if (fernandoProfile) { + console.log('✅ Fernando encontrado na tabela profiles:'); + console.log(JSON.stringify(fernandoProfile, null, 2)); + } else { + console.log('❌ Fernando NÃO encontrado na tabela profiles\n'); + } + + // Buscar nos pacientes também + console.log('\n🔍 Buscando fernando na tabela patients...'); + const patientsRes = await axios.get(`${BASE_URL}/rest/v1/patients?select=*`, { + headers: { + 'apikey': ANON_KEY, + 'Authorization': `Bearer ${token}` + } + }); + + console.log(`📊 Total de patients: ${patientsRes.data.length}\n`); + + const fernandoPatient = patientsRes.data.find(p => + p.email && ( + p.email.toLowerCase().includes('fernando') || + p.full_name?.toLowerCase().includes('fernando') + ) + ); + + if (fernandoPatient) { + console.log('✅ Fernando encontrado na tabela patients:'); + console.log(JSON.stringify(fernandoPatient, null, 2)); + } else { + console.log('❌ Fernando NÃO encontrado na tabela patients\n'); + } + + // Listar alguns emails para referência + if (!fernandoProfile && !fernandoPatient) { + console.log('\n📧 Alguns emails cadastrados nos profiles:'); + profilesRes.data.slice(0, 10).forEach((u, i) => { + if (u.email) console.log(` ${i+1}. ${u.email} - ${u.full_name || 'sem nome'}`); + }); + } + + } catch (err) { + console.error('❌ Erro:', err.response?.data || err.message); + if (err.response) { + console.error('Status:', err.response.status); + console.error('Headers:', err.response.headers); + } + } +})(); diff --git a/MEDICONNECT 2/src/App.tsx b/MEDICONNECT 2/src/App.tsx index d9e2415bc..2be57c449 100644 --- a/MEDICONNECT 2/src/App.tsx +++ b/MEDICONNECT 2/src/App.tsx @@ -26,6 +26,7 @@ import PerfilMedico from "./pages/PerfilMedico"; import PerfilPaciente from "./pages/PerfilPaciente"; import ClearCache from "./pages/ClearCache"; import AuthCallback from "./pages/AuthCallback"; +import ResetPassword from "./pages/ResetPassword"; function App() { return ( @@ -48,6 +49,7 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/MEDICONNECT 2/src/components/Chatbot.tsx b/MEDICONNECT 2/src/components/Chatbot.tsx new file mode 100644 index 000000000..3c4c108ea --- /dev/null +++ b/MEDICONNECT 2/src/components/Chatbot.tsx @@ -0,0 +1,277 @@ +import React, { useState, useRef, useEffect } from "react"; +import { MessageCircle, X, Send } from "lucide-react"; + +interface Message { + id: string; + text: string; + sender: "user" | "bot"; + timestamp: Date; +} + +interface ChatbotProps { + className?: string; +} + +const Chatbot: React.FC = ({ className = "" }) => { + const [isOpen, setIsOpen] = useState(false); + const [messages, setMessages] = useState([ + { + id: "welcome", + text: "Olá! Sou o assistente virtual do MediConnect. Como posso ajudá-lo hoje?", + sender: "bot", + timestamp: new Date(), + }, + ]); + const [inputValue, setInputValue] = useState(""); + const [isTyping, setIsTyping] = useState(false); + const messagesEndRef = useRef(null); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }; + + useEffect(() => { + scrollToBottom(); + }, [messages]); + + const quickReplies = [ + "Como agendar uma consulta?", + "Como cancelar agendamento?", + "Esqueci minha senha", + "Suporte técnico", + ]; + + const getBotResponse = (userMessage: string): string => { + const message = userMessage.toLowerCase(); + + // Respostas baseadas em palavras-chave + if (message.includes("agendar") || message.includes("marcar")) { + return "Para agendar uma consulta:\n\n1. Acesse 'Agendar Consulta' no menu\n2. Selecione o médico desejado\n3. Escolha data e horário disponível\n4. Confirme o agendamento\n\nVocê receberá uma confirmação por e-mail!"; + } + + if (message.includes("cancelar") || message.includes("remarcar")) { + return "Para cancelar ou remarcar uma consulta:\n\n1. Vá em 'Minhas Consultas'\n2. Localize a consulta\n3. Clique em 'Cancelar' ou 'Remarcar'\n\nRecomendamos fazer isso com 24h de antecedência para evitar taxas."; + } + + if (message.includes("senha") || message.includes("login")) { + return "Para recuperar sua senha:\n\n1. Clique em 'Esqueceu a senha?' na tela de login\n2. Insira seu e-mail cadastrado\n3. Você receberá um link para redefinir a senha\n\nSe não receber o e-mail, verifique sua caixa de spam."; + } + + if (message.includes("pagamento") || message.includes("pagar")) { + return "Aceitamos as seguintes formas de pagamento:\n\n• Cartão de crédito (parcelamento em até 3x)\n• Cartão de débito\n• PIX\n• Boleto bancário\n\nTodos os pagamentos são processados com segurança."; + } + + if (message.includes("teleconsulta") || message.includes("online")) { + return "Para realizar uma teleconsulta:\n\n1. Acesse 'Minhas Consultas' no horário agendado\n2. Clique em 'Iniciar Consulta Online'\n3. Permita acesso à câmera e microfone\n\nCertifique-se de ter uma boa conexão de internet!"; + } + + if (message.includes("histórico") || message.includes("prontuário")) { + return "Seu histórico médico pode ser acessado em:\n\n• 'Meu Perfil' > 'Histórico Médico'\n• 'Minhas Consultas' (consultas anteriores)\n\nVocê pode fazer download de relatórios e receitas quando necessário."; + } + + if ( + message.includes("suporte") || + message.includes("ajuda") || + message.includes("atendimento") + ) { + return "Nossa equipe de suporte está disponível:\n\n📞 Telefone: 0800-123-4567\n📧 E-mail: suporte@mediconnect.com.br\n⏰ Horário: Segunda a Sexta, 8h às 18h\n\nVocê também pode acessar nossa Central de Ajuda completa no menu."; + } + + if (message.includes("obrigad") || message.includes("valeu")) { + return "Por nada! Estou sempre aqui para ajudar. Se tiver mais dúvidas, é só chamar! 😊"; + } + + if ( + message.includes("oi") || + message.includes("olá") || + message.includes("hello") + ) { + return "Olá! Como posso ajudá-lo hoje? Você pode perguntar sobre agendamentos, consultas, pagamentos ou qualquer dúvida sobre o MediConnect."; + } + + // Resposta padrão + return "Desculpe, não entendi sua pergunta. Você pode:\n\n• Perguntar sobre agendamentos\n• Consultar formas de pagamento\n• Saber sobre teleconsultas\n• Acessar histórico médico\n• Falar com suporte\n\nOu visite nossa Central de Ajuda para mais informações!"; + }; + + const handleSend = () => { + if (!inputValue.trim()) return; + + // Adiciona mensagem do usuário + const userMessage: Message = { + id: Date.now().toString(), + text: inputValue, + sender: "user", + timestamp: new Date(), + }; + setMessages((prev) => [...prev, userMessage]); + setInputValue(""); + + // Simula digitação do bot + setIsTyping(true); + setTimeout(() => { + const botResponse: Message = { + id: (Date.now() + 1).toString(), + text: getBotResponse(inputValue), + sender: "bot", + timestamp: new Date(), + }; + setMessages((prev) => [...prev, botResponse]); + setIsTyping(false); + }, 1000); + }; + + const handleQuickReply = (reply: string) => { + setInputValue(reply); + }; + + const handleKeyPress = (e: React.KeyboardEvent) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + handleSend(); + } + }; + + return ( +
+ {/* Floating Button */} + {!isOpen && ( + + )} + + {/* Chat Window */} + {isOpen && ( +
+ {/* Header */} +
+
+
+ +
+
+

Assistente MediConnect

+

+ Online • Responde em segundos +

+
+
+ +
+ + {/* Messages */} +
+ {messages.map((message) => ( +
+
+

{message.text}

+

+ {message.timestamp.toLocaleTimeString("pt-BR", { + hour: "2-digit", + minute: "2-digit", + })} +

+
+
+ ))} + + {isTyping && ( +
+
+
+
+
+
+
+
+
+ )} + +
+
+ + {/* Quick Replies */} + {messages.length <= 1 && ( +
+

+ Perguntas frequentes: +

+
+ {quickReplies.map((reply, index) => ( + + ))} +
+
+ )} + + {/* Input */} +
+
+ setInputValue(e.target.value)} + onKeyPress={handleKeyPress} + placeholder="Digite sua mensagem..." + className="flex-1 border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> + +
+
+
+ )} +
+ ); +}; + +export default Chatbot; diff --git a/MEDICONNECT 2/src/components/HeroBanner.tsx b/MEDICONNECT 2/src/components/HeroBanner.tsx new file mode 100644 index 000000000..7b54679a5 --- /dev/null +++ b/MEDICONNECT 2/src/components/HeroBanner.tsx @@ -0,0 +1,151 @@ +import React, { useState, useEffect } from "react"; +import { Calendar, Clock, ArrowRight } from "lucide-react"; +import { useNavigate } from "react-router-dom"; +import { i18n } from "../i18n"; + +// Importar as imagens +import medico1 from "./images/medico1.jpg"; +import medico2 from "./images/medico2.jpg"; +import medico3 from "./images/medico3.jpg"; + +const images = [medico1, medico2, medico3]; + +export const HeroBanner: React.FC = () => { + const [currentImageIndex, setCurrentImageIndex] = useState(0); + const [nextImageIndex, setNextImageIndex] = useState(1); + const [isTransitioning, setIsTransitioning] = useState(false); + const navigate = useNavigate(); + + useEffect(() => { + // Rotacionar imagens a cada 5 segundos + const interval = setInterval(() => { + setIsTransitioning(true); + + // Após 2 segundos (duração da transição), atualizar os índices + setTimeout(() => { + setCurrentImageIndex((prev) => (prev + 1) % images.length); + setNextImageIndex((prev) => (prev + 1) % images.length); + setIsTransitioning(false); + }, 2000); + }, 5000); + + return () => clearInterval(interval); + }, []); + + const handleCTA = (action: string, destination: string) => { + console.log(`CTA clicked: ${action} -> ${destination}`); + navigate(destination); + }; + + return ( +
+ {/* Background Images com Fade Transition */} +
+ {/* Imagem Atual */} +
+ + {/* Próxima Imagem (para transição suave) */} +
+ + {/* Overlay Azul Translúcido */} +
+ + {/* Decorative Pattern */} +
+ + + + + + + + +
+
+ + {/* Conteúdo */} +
+

+ {i18n.t("home.hero.title")} +

+

+ {i18n.t("home.hero.subtitle")} +

+ + {/* CTAs */} +
+ + + +
+
+ + {/* Indicadores de Imagem (opcionais - pequenos pontos na parte inferior) */} +
+ {images.map((_, index) => ( +
+
+ ); +}; diff --git a/MEDICONNECT 2/src/components/images/medico1.jpg b/MEDICONNECT 2/src/components/images/medico1.jpg new file mode 100644 index 000000000..6caeaf733 Binary files /dev/null and b/MEDICONNECT 2/src/components/images/medico1.jpg differ diff --git a/MEDICONNECT 2/src/components/images/medico2.jpg b/MEDICONNECT 2/src/components/images/medico2.jpg new file mode 100644 index 000000000..8c8584d3a Binary files /dev/null and b/MEDICONNECT 2/src/components/images/medico2.jpg differ diff --git a/MEDICONNECT 2/src/components/images/medico3.jpg b/MEDICONNECT 2/src/components/images/medico3.jpg new file mode 100644 index 000000000..40e243876 Binary files /dev/null and b/MEDICONNECT 2/src/components/images/medico3.jpg differ diff --git a/MEDICONNECT 2/src/components/secretaria/SecretaryAppointmentList.tsx b/MEDICONNECT 2/src/components/secretaria/SecretaryAppointmentList.tsx index e044277e8..e8c9c8037 100644 --- a/MEDICONNECT 2/src/components/secretaria/SecretaryAppointmentList.tsx +++ b/MEDICONNECT 2/src/components/secretaria/SecretaryAppointmentList.tsx @@ -24,6 +24,16 @@ export function SecretaryAppointmentList() { const [searchTerm, setSearchTerm] = useState(""); const [statusFilter, setStatusFilter] = useState("Todos"); const [typeFilter, setTypeFilter] = useState("Todos"); + const [showCreateModal, setShowCreateModal] = useState(false); + const [patients, setPatients] = useState([]); + const [doctors, setDoctors] = useState([]); + const [formData, setFormData] = useState({ + patient_id: "", + doctor_id: "", + scheduled_at: "", + appointment_type: "presencial", + notes: "", + }); const loadAppointments = async () => { setLoading(true); @@ -68,8 +78,60 @@ export function SecretaryAppointmentList() { useEffect(() => { loadAppointments(); + loadDoctorsAndPatients(); }, []); + const loadDoctorsAndPatients = async () => { + try { + const [patientsData, doctorsData] = await Promise.all([ + patientService.list(), + doctorService.list(), + ]); + setPatients(Array.isArray(patientsData) ? patientsData : []); + setDoctors(Array.isArray(doctorsData) ? doctorsData : []); + } catch (error) { + console.error("Erro ao carregar pacientes e médicos:", error); + } + }; + + const handleOpenCreateModal = () => { + setFormData({ + patient_id: "", + doctor_id: "", + scheduled_at: "", + appointment_type: "presencial", + notes: "", + }); + setShowCreateModal(true); + }; + + const handleCreateAppointment = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!formData.patient_id || !formData.doctor_id || !formData.scheduled_at) { + toast.error("Preencha todos os campos obrigatórios"); + return; + } + + try { + await appointmentService.create({ + patient_id: formData.patient_id, + doctor_id: formData.doctor_id, + scheduled_at: new Date(formData.scheduled_at).toISOString(), + appointment_type: formData.appointment_type as + | "presencial" + | "telemedicina", + }); + + toast.success("Consulta agendada com sucesso!"); + setShowCreateModal(false); + loadAppointments(); + } catch (error) { + console.error("Erro ao criar consulta:", error); + toast.error("Erro ao agendar consulta"); + } + }; + const handleSearch = () => { loadAppointments(); }; @@ -137,7 +199,10 @@ export function SecretaryAppointmentList() {

Consultas

Gerencie as consultas agendadas

- @@ -346,6 +411,116 @@ export function SecretaryAppointmentList() {
+ + {/* Modal de Criar Consulta */} + {showCreateModal && ( +
+
+
+

+ Nova Consulta +

+
+ +
+
+
+ + +
+ +
+ + +
+ +
+ + + setFormData({ ...formData, scheduled_at: e.target.value }) + } + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500" + required + /> +
+ +
+ + +
+
+ +
+ + +
+
+
+
+ )}
); } diff --git a/MEDICONNECT 2/src/components/secretaria/SecretaryDoctorSchedule.tsx b/MEDICONNECT 2/src/components/secretaria/SecretaryDoctorSchedule.tsx index 126d1a5c9..5905fa7f0 100644 --- a/MEDICONNECT 2/src/components/secretaria/SecretaryDoctorSchedule.tsx +++ b/MEDICONNECT 2/src/components/secretaria/SecretaryDoctorSchedule.tsx @@ -58,6 +58,7 @@ export function SecretaryDoctorSchedule() { setLoading(true); try { + // Load availabilities const availData = await availabilityService.list({ doctor_id: selectedDoctorId, }); @@ -68,7 +69,6 @@ export function SecretaryDoctorSchedule() { } catch (error) { console.error("Erro ao carregar agenda:", error); toast.error("Erro ao carregar agenda do médico"); - setAvailabilities([]); } finally { setLoading(false); } @@ -145,46 +145,14 @@ export function SecretaryDoctorSchedule() { return; } - if (!selectedDoctorId) { - toast.error("Selecione um médico"); - return; - } - try { - console.log("📤 Criando disponibilidades para os dias:", selectedWeekdays); - - // Cria uma disponibilidade para cada dia da semana selecionado - for (const weekday of selectedWeekdays) { - const availabilityData: any = { - doctor_id: selectedDoctorId, - weekday: weekday, - start_time: `${startTime}:00`, - end_time: `${endTime}:00`, - slot_minutes: duration, - appointment_type: "presencial", - active: true, - }; - - console.log("📤 Tentando criar disponibilidade:", availabilityData); - - const result = await availabilityService.create(availabilityData); - console.log("✅ Disponibilidade criada:", result); - } - - toast.success(`${selectedWeekdays.length} disponibilidade(s) criada(s) com sucesso!`); + // TODO: Implement availability creation + toast.success("Disponibilidade adicionada com sucesso"); setShowAvailabilityDialog(false); - - // Limpa o formulário - setSelectedWeekdays([]); - setStartTime("08:00"); - setEndTime("18:00"); - setDuration(30); - - // Recarrega as disponibilidades - await loadDoctorSchedule(); + loadDoctorSchedule(); } catch (error) { - console.error("❌ Erro ao adicionar disponibilidade:", error); - toast.error("Erro ao adicionar disponibilidade. Verifique as permissões no banco de dados."); + console.error("Erro ao adicionar disponibilidade:", error); + toast.error("Erro ao adicionar disponibilidade"); } }; @@ -204,14 +172,15 @@ export function SecretaryDoctorSchedule() { toast.error("Erro ao adicionar exceção"); } }; + const weekdays = [ - { value: "segunda", label: "Segunda" }, - { value: "terca", label: "Terça" }, - { value: "quarta", label: "Quarta" }, - { value: "quinta", label: "Quinta" }, - { value: "sexta", label: "Sexta" }, - { value: "sabado", label: "Sábado" }, - { value: "domingo", label: "Domingo" }, + { value: "monday", label: "Segunda" }, + { value: "tuesday", label: "Terça" }, + { value: "wednesday", label: "Quarta" }, + { value: "thursday", label: "Quinta" }, + { value: "friday", label: "Sexta" }, + { value: "saturday", label: "Sábado" }, + { value: "sunday", label: "Domingo" }, ]; return ( @@ -343,16 +312,16 @@ export function SecretaryDoctorSchedule() { className="flex items-center justify-between p-4 bg-gray-50 rounded-lg" >
-

- {avail.weekday || "Não especificado"} +

+ {avail.day_of_week}

- {avail.start_time} - {avail.end_time} ({avail.slot_minutes || 30} min/consulta) + {avail.start_time} - {avail.end_time}

- {avail.active !== false ? "Ativo" : "Inativo"} + Ativo - -
-
- - {/* Search and Filters */} -
-
-
- - setSearchTerm(e.target.value)} - className="w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent" - /> -
- - -
- -
- - -
- Convênio: - -
-
-
- - {/* Table */} -
- - - - - - - - - - - {loading ? ( - - - - ) : patients.length === 0 ? ( - - - - ) : ( - patients.map((patient, index) => ( - - - - - - - )) - )} - -
- Paciente - - Próximo Atendimento - - Convênio - - Ações -
- Carregando pacientes... -
- Nenhum paciente encontrado -
-
- -
-

- {patient.full_name} -

-

{patient.email}

-

- {patient.phone_mobile} -

-
-
-
- {/* TODO: Buscar próximo agendamento */}— - - Particular - -
- - - - -
-
-
- - {/* Modal de Formulário */} - {showModal && ( -
-
- {/* Header */} -
-

- {modalMode === "create" ? "Novo Paciente" : "Editar Paciente"} -

- -
- - {/* Form Content */} -
- -
-
-
- )} - - ); -} diff --git a/MEDICONNECT 2/src/components/secretaria/SecretaryPatientList.tsx b/MEDICONNECT 2/src/components/secretaria/SecretaryPatientList.tsx index 8ac0bfe2d..152985fcb 100644 --- a/MEDICONNECT 2/src/components/secretaria/SecretaryPatientList.tsx +++ b/MEDICONNECT 2/src/components/secretaria/SecretaryPatientList.tsx @@ -1,10 +1,9 @@ import { useState, useEffect } from "react"; import toast from "react-hot-toast"; -import { Search, Plus, Eye, Calendar, Edit, Trash2, X, RefreshCw } from "lucide-react"; -import { patientService, type Patient } from "../../services"; +import { Search, Plus, Eye, Calendar, Edit, Trash2, X } from "lucide-react"; +import { patientService, userService, type Patient } from "../../services"; import PacienteForm, { type PacienteFormData } from "../pacientes/PacienteForm"; import { Avatar } from "../ui/Avatar"; -import { validarCPF } from "../../utils/validators"; const BLOOD_TYPES = ["A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"]; @@ -84,51 +83,14 @@ export function SecretaryPatientList() { const loadPatients = async () => { setLoading(true); try { - console.log("🔄 Carregando lista de pacientes..."); const data = await patientService.list(); - console.log("✅ Pacientes carregados:", { - total: Array.isArray(data) ? data.length : 0, - isArray: Array.isArray(data), - dataType: typeof data, - primeiros3: Array.isArray(data) ? data.slice(0, 3) : null, - todosOsDados: data - }); - - // Verifica se há pacientes e se eles têm os campos necessários - if (Array.isArray(data) && data.length > 0) { - console.log("📋 Exemplo do primeiro paciente:", data[0]); - console.log("📋 Campos disponíveis:", Object.keys(data[0] || {})); - - // Busca específica pelo paciente "teste squad 18" - const testeSquad = data.find(p => - p.full_name?.toLowerCase().includes("teste squad") - ); - if (testeSquad) { - console.log("✅ PACIENTE 'teste squad 18' ENCONTRADO:", testeSquad); - } else { - console.warn("❌ PACIENTE 'teste squad 18' NÃO ENCONTRADO"); - console.log("📋 Lista de nomes dos pacientes:", data.map(p => p.full_name)); - } - } - - console.log("💾 Atualizando estado com", data.length, "pacientes"); + console.log("✅ Pacientes carregados:", data); setPatients(Array.isArray(data) ? data : []); - - // Verifica o estado logo após setar - console.log("💾 Estado patients após setPatients:", { - length: Array.isArray(data) ? data.length : 0, - isArray: Array.isArray(data) - }); - if (Array.isArray(data) && data.length === 0) { console.warn("⚠️ Nenhum paciente encontrado na API"); } - } catch (error: any) { - console.error("❌ Erro ao carregar pacientes:", { - message: error?.message, - response: error?.response?.data, - status: error?.response?.status - }); + } catch (error) { + console.error("❌ Erro ao carregar pacientes:", error); toast.error("Erro ao carregar pacientes"); setPatients([]); } finally { @@ -273,120 +235,34 @@ export function SecretaryPatientList() { await patientService.update(formData.id, patientData); toast.success("Paciente atualizado com sucesso!"); } else { - // Para criação, apenas cria o registro na tabela patients - // O usuário de autenticação pode ser criado depois quando necessário - - // Validação dos campos obrigatórios no frontend - if (!formData.email || !formData.nome || !formData.cpf) { - toast.error("Por favor, preencha os campos obrigatórios: Email, Nome e CPF"); - return; - } - - // Remove formatação do CPF (deixa apenas números) - const cpfLimpo = formData.cpf.replace(/\D/g, ""); - - if (cpfLimpo.length !== 11) { - toast.error("CPF deve ter 11 dígitos"); - return; - } - - // Valida CPF - if (!validarCPF(cpfLimpo)) { - toast.error("CPF inválido. Verifique os dígitos verificadores."); - return; - } - - // Monta o telefone completo - const ddd = (formData.ddd || "").replace(/\D/g, ""); - const numero = (formData.numeroTelefone || "").replace(/\D/g, ""); - - // Validação do telefone - if (!ddd || !numero) { - toast.error("Por favor, preencha o DDD e o número do telefone"); - return; - } - - if (ddd.length !== 2) { - toast.error("DDD deve ter 2 dígitos"); - return; - } - - if (numero.length < 8 || numero.length > 9) { - toast.error("Número do telefone deve ter 8 ou 9 dígitos"); - return; - } - - // Monta telefone no formato: (11) 99999-9999 - const telefoneLimpo = `(${ddd}) ${numero.length === 9 ? numero.substring(0, 5) + '-' + numero.substring(5) : numero.substring(0, 4) + '-' + numero.substring(4)}`; - - // Cria apenas o registro na tabela patients - const patientData = { - full_name: formData.nome.trim(), - cpf: cpfLimpo, - email: formData.email.trim(), - phone_mobile: telefoneLimpo, - birth_date: formData.dataNascimento || null, - sex: formData.sexo || null, - blood_type: formData.tipo_sanguineo || null, - // Converte altura de cm para metros (ex: 180 cm = 1.80 m) - height_m: formData.altura && !isNaN(parseFloat(formData.altura)) - ? parseFloat(formData.altura) / 100 - : null, - weight_kg: formData.peso && !isNaN(parseFloat(formData.peso)) - ? parseFloat(formData.peso) - : null, - cep: formData.endereco.cep || null, - street: formData.endereco.rua || null, - number: formData.endereco.numero || null, - complement: formData.endereco.complemento || null, - neighborhood: formData.endereco.bairro || null, - city: formData.endereco.cidade || null, - state: formData.endereco.estado || null, + // Para criação, usa o novo endpoint create-patient com validações completas + const createData = { + email: formData.email, + full_name: formData.nome, + cpf: formData.cpf, + phone_mobile: formData.numeroTelefone, + birth_date: formData.dataNascimento || undefined, + address: formData.endereco.rua + ? `${formData.endereco.rua}${ + formData.endereco.numero ? ", " + formData.endereco.numero : "" + }${ + formData.endereco.bairro ? " - " + formData.endereco.bairro : "" + }${ + formData.endereco.cidade ? " - " + formData.endereco.cidade : "" + }${ + formData.endereco.estado ? "/" + formData.endereco.estado : "" + }` + : undefined, }; - - console.log("📤 Criando registro de paciente:", patientData); - console.log("📤 Tipos dos campos:", { - height_m: typeof patientData.height_m, - weight_kg: typeof patientData.weight_kg, - height_value: patientData.height_m, - weight_value: patientData.weight_kg, - }); - const patientResult = await patientService.create(patientData); - console.log("✅ Paciente criado na tabela patients:", patientResult); - + await userService.createPatient(createData); toast.success("Paciente cadastrado com sucesso!"); } setShowModal(false); - - // Aguarda um pouco antes de recarregar para o banco propagar - console.log("⏳ Aguardando 1 segundo antes de recarregar a lista..."); - await new Promise(resolve => setTimeout(resolve, 1000)); - - console.log("🔄 Recarregando lista de pacientes..."); - await loadPatients(); - } catch (error: any) { - console.error("❌ Erro ao salvar paciente:", error); - console.error("❌ Detalhes do erro:", { - message: error?.message, - response: error?.response, - responseData: error?.response?.data, - status: error?.response?.status, - statusText: error?.response?.statusText, - }); - - // Exibe mensagem de erro mais específica - let errorMessage = "Erro ao salvar paciente"; - - if (error?.response?.data) { - const data = error.response.data; - errorMessage = data.error || data.message || data.details || JSON.stringify(data); - } else if (error?.message) { - errorMessage = error.message; - } - - console.error("❌ Mensagem final de erro:", errorMessage); - toast.error(errorMessage); + loadPatients(); + } catch (error) { + console.error("Erro ao salvar paciente:", error); + toast.error("Erro ao salvar paciente"); } finally { setLoading(false); } @@ -405,14 +281,6 @@ export function SecretaryPatientList() { return colors[index % colors.length]; }; - // Log para debug do estado atual - console.log("🎨 Renderizando SecretaryPatientList:", { - totalPacientes: patients.length, - loading: loading, - temPacientes: patients.length > 0, - primeiros2: patients.slice(0, 2) - }); - return (
{/* Header */} @@ -423,23 +291,13 @@ export function SecretaryPatientList() { Gerencie os pacientes cadastrados

-
- - -
+ {/* Search and Filters */} @@ -607,8 +465,7 @@ export function SecretaryPatientList() { )) - ) - } + )} diff --git a/MEDICONNECT 2/src/components/secretaria/SecretaryReportList.tsx b/MEDICONNECT 2/src/components/secretaria/SecretaryReportList.tsx index e042ae295..64027ba77 100644 --- a/MEDICONNECT 2/src/components/secretaria/SecretaryReportList.tsx +++ b/MEDICONNECT 2/src/components/secretaria/SecretaryReportList.tsx @@ -1,7 +1,12 @@ import { useState, useEffect } from "react"; import toast from "react-hot-toast"; -import { Search, FileText, Download } from "lucide-react"; -import { reportService, type Report } from "../../services"; +import { Search, FileText, Download, Plus } from "lucide-react"; +import { + reportService, + type Report, + patientService, + type Patient, +} from "../../services"; export function SecretaryReportList() { const [reports, setReports] = useState([]); @@ -9,11 +14,64 @@ export function SecretaryReportList() { const [searchTerm, setSearchTerm] = useState(""); const [typeFilter, setTypeFilter] = useState("Todos"); const [periodFilter, setPeriodFilter] = useState("Todos"); + const [showCreateModal, setShowCreateModal] = useState(false); + const [patients, setPatients] = useState([]); + const [formData, setFormData] = useState({ + patient_id: "", + exam: "", + diagnosis: "", + conclusion: "", + }); useEffect(() => { loadReports(); + loadPatients(); }, []); + const loadPatients = async () => { + try { + const data = await patientService.list(); + setPatients(Array.isArray(data) ? data : []); + } catch (error) { + console.error("Erro ao carregar pacientes:", error); + } + }; + + const handleOpenCreateModal = () => { + setFormData({ + patient_id: "", + exam: "", + diagnosis: "", + conclusion: "", + }); + setShowCreateModal(true); + }; + + const handleCreateReport = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!formData.patient_id) { + toast.error("Selecione um paciente"); + return; + } + + try { + await reportService.create({ + patient_id: formData.patient_id, + exam: formData.exam, + diagnosis: formData.diagnosis, + conclusion: formData.conclusion, + }); + + toast.success("Relatório criado com sucesso!"); + setShowCreateModal(false); + loadReports(); + } catch (error) { + console.error("Erro ao criar relatório:", error); + toast.error("Erro ao criar relatório"); + } + }; + const loadReports = async () => { setLoading(true); try { @@ -68,9 +126,12 @@ export function SecretaryReportList() { Visualize e baixe relatórios do sistema

- @@ -242,6 +303,103 @@ export function SecretaryReportList() { + + {/* Modal de Criar Relatório */} + {showCreateModal && ( +
+
+
+

+ Novo Relatório +

+
+ +
+
+
+ + +
+ +
+ + + setFormData({ ...formData, exam: e.target.value }) + } + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500" + placeholder="Nome do exame realizado" + /> +
+ +
+ +