From 7ef8715f63a86589e32d2c9d48c7bba7a52a7930 Mon Sep 17 00:00:00 2001 From: guisilvagomes Date: Tue, 28 Oct 2025 11:01:15 -0300 Subject: [PATCH] feat: atualizar API de avatar para Supabase Storage + adicionar avatar no painel do paciente --- .../src/components/ui/AvatarUpload.tsx | 4 +- .../src/pages/AcompanhamentoPaciente.tsx | 34 +++++--- .../src/services/avatars/avatarService.ts | 81 +++++++++++-------- 3 files changed, 76 insertions(+), 43 deletions(-) diff --git a/MEDICONNECT 2/src/components/ui/AvatarUpload.tsx b/MEDICONNECT 2/src/components/ui/AvatarUpload.tsx index c154435f2..2d1e480f0 100644 --- a/MEDICONNECT 2/src/components/ui/AvatarUpload.tsx +++ b/MEDICONNECT 2/src/components/ui/AvatarUpload.tsx @@ -154,10 +154,10 @@ export function AvatarUpload({ )} diff --git a/MEDICONNECT 2/src/pages/AcompanhamentoPaciente.tsx b/MEDICONNECT 2/src/pages/AcompanhamentoPaciente.tsx index 253b965f1..ac09b8fde 100644 --- a/MEDICONNECT 2/src/pages/AcompanhamentoPaciente.tsx +++ b/MEDICONNECT 2/src/pages/AcompanhamentoPaciente.tsx @@ -20,9 +20,10 @@ import { format } from "date-fns"; import { ptBR } from "date-fns/locale"; import { useNavigate } from "react-router-dom"; import { useAuth } from "../hooks/useAuth"; -import { appointmentService, doctorService, reportService } from "../services"; +import { appointmentService, doctorService, reportService, profileService } from "../services"; import type { Report } from "../services/reports/types"; import AgendamentoConsulta from "../components/AgendamentoConsulta"; +import { Avatar } from "../components/ui/Avatar"; interface Consulta { _id: string; @@ -66,6 +67,7 @@ const AcompanhamentoPaciente: React.FC = () => { const [especialidadeFiltro, setEspecialidadeFiltro] = useState(""); const [laudos, setLaudos] = useState([]); const [loadingLaudos, setLoadingLaudos] = useState(false); + const [avatarUrl, setAvatarUrl] = useState(undefined); const pacienteId = user?.id || ""; const pacienteNome = user?.nome || "Paciente"; @@ -138,6 +140,22 @@ const AcompanhamentoPaciente: React.FC = () => { fetchConsultas(); }, [fetchConsultas]); + // Carregar avatar do perfil + useEffect(() => { + const loadAvatar = async () => { + if (!pacienteId) return; + try { + const profile = await profileService.getById(pacienteId); + if (profile?.avatar_url) { + setAvatarUrl(profile.avatar_url); + } + } catch { + console.log("Perfil não encontrado, usando avatar padrão"); + } + }; + loadAvatar(); + }, [pacienteId]); + // Recarregar consultas quando mudar para a aba de consultas const fetchLaudos = useCallback(async () => { if (!pacienteId) return; @@ -282,14 +300,12 @@ const AcompanhamentoPaciente: React.FC = () => { {/* Patient Profile */}
-
- {pacienteNome - .split(" ") - .map((n) => n[0]) - .join("") - .toUpperCase() - .slice(0, 2)} -
+

{pacienteNome} diff --git a/MEDICONNECT 2/src/services/avatars/avatarService.ts b/MEDICONNECT 2/src/services/avatars/avatarService.ts index 049d05e69..5443ec927 100644 --- a/MEDICONNECT 2/src/services/avatars/avatarService.ts +++ b/MEDICONNECT 2/src/services/avatars/avatarService.ts @@ -2,7 +2,8 @@ * Serviço de Avatars (Frontend) */ -import { apiClient } from "../api/client"; +import axios from "axios"; +import { API_CONFIG } from "../api/config"; import type { UploadAvatarInput, UploadAvatarResponse, @@ -11,58 +12,75 @@ import type { } from "./types"; class AvatarService { + private readonly SUPABASE_URL = API_CONFIG.SUPABASE_URL; + private readonly STORAGE_URL = `${this.SUPABASE_URL}/storage/v1/object/avatars`; + /** * Faz upload de avatar do usuário */ async upload(data: UploadAvatarInput): Promise { try { - // Converte arquivo para base64 - const fileData = await this.fileToBase64(data.file); + const token = localStorage.getItem(API_CONFIG.STORAGE_KEYS.ACCESS_TOKEN); + + // Determina a extensão do arquivo + const ext = data.file.name.split(".").pop()?.toLowerCase() || "jpg"; + const path = `${data.userId}/avatar.${ext}`; - const response = await apiClient.post( - `/avatars-upload?userId=${data.userId}`, - { - fileData, - contentType: data.file.type, - fileName: data.file.name, - }, + // Cria FormData para o upload + const formData = new FormData(); + formData.append("file", data.file); + + // Upload usando Supabase Storage API + await axios.post( + `${this.STORAGE_URL}/${path}`, + formData, { headers: { - "Content-Type": "application/json", + "Authorization": `Bearer ${token}`, + "Content-Type": "multipart/form-data", }, } ); - return response.data; + // Retorna a URL pública + const publicUrl = this.getPublicUrl({ + userId: data.userId, + ext: ext as "jpg" | "png" | "webp", + }); + + return { + Key: publicUrl, + }; } catch (error) { console.error("Erro ao fazer upload do avatar:", error); throw error; } } - /** - * Converte File para base64 - */ - private fileToBase64(file: File): Promise { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => { - const result = reader.result as string; - // Remove o prefixo "data:image/...;base64," - const base64 = result.split(",")[1]; - resolve(base64); - }; - reader.onerror = reject; - reader.readAsDataURL(file); - }); - } - /** * Remove avatar do usuário */ async delete(data: DeleteAvatarInput): Promise { try { - await apiClient.delete(`/avatars-delete?userId=${data.userId}`); + const token = localStorage.getItem(API_CONFIG.STORAGE_KEYS.ACCESS_TOKEN); + + // Tenta deletar todas as extensões possíveis + const extensions = ["jpg", "png", "webp"]; + + for (const ext of extensions) { + try { + await axios.delete( + `${this.STORAGE_URL}/${data.userId}/avatar.${ext}`, + { + headers: { + "Authorization": `Bearer ${token}`, + }, + } + ); + } catch { + // Ignora erros se o arquivo não existir + } + } } catch (error) { console.error("Erro ao deletar avatar:", error); throw error; @@ -74,8 +92,7 @@ class AvatarService { * Não precisa de autenticação pois é endpoint público */ getPublicUrl(data: GetAvatarUrlInput): string { - const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; - return `${SUPABASE_URL}/storage/v1/object/public/avatars/${data.userId}/avatar.${data.ext}`; + return `${this.STORAGE_URL}/${data.userId}/avatar.${data.ext}`; } }