diff --git a/app/patient/profile/page.tsx b/app/patient/profile/page.tsx index fb03df2..9641a1e 100644 --- a/app/patient/profile/page.tsx +++ b/app/patient/profile/page.tsx @@ -1,18 +1,17 @@ -// ARQUIVO COMPLETO PARA: app/patient/profile/page.tsx - +// Caminho: app/patient/profile/page.tsx "use client"; import { useState, useEffect, useRef } from "react"; import Sidebar from "@/components/Sidebar"; import { useAuthLayout } from "@/hooks/useAuthLayout"; import { patientsService } from "@/services/patientsApi.mjs"; +import { usersService } from "@/services/usersApi.mjs"; // Adicionado import import { api } from "@/services/api.mjs"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { Textarea } from "@/components/ui/textarea"; import { User, Mail, Phone, Calendar, Upload } from "lucide-react"; import { toast } from "@/hooks/use-toast"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; @@ -40,43 +39,63 @@ export default function PatientProfile() { const [isSaving, setIsSaving] = useState(false); const fileInputRef = useRef(null); - useEffect(() => { - console.log("PatientProfile MONTADO"); -}, []); - - - useEffect(() => { - const userId = user?.id; - if (!userId) return; - - const fetchPatientDetails = async () => { - try { - const patientDetails = await patientsService.getById(userId); - setPatientData({ - name: patientDetails.full_name || user.name, - email: user.email, - phone: patientDetails.phone_mobile || "", - cpf: patientDetails.cpf || "", - birthDate: patientDetails.birth_date || "", - cep: patientDetails.cep || "", - street: patientDetails.street || "", - number: patientDetails.number || "", - city: patientDetails.city || "", - avatarFullUrl: user.avatarFullUrl, - }); - } catch (error) { - console.error("Erro ao buscar detalhes do paciente:", error); - toast({ - title: "Erro", - description: "Não foi possível carregar seus dados completos.", - variant: "destructive", - }); - } + const getInitials = (name: string) => { + if (!name) return "U"; + return name + .split(" ") + .map((n) => n[0]) + .slice(0, 2) + .join("") + .toUpperCase(); }; - fetchPatientDetails(); -}, [user?.id, user?.name, user?.email, user?.avatarFullUrl]); + // Função auxiliar para construir URL do avatar + const buildAvatarUrl = (path: string | null | undefined) => { + if (!path) return undefined; + const baseUrl = "https://yuanqfswhberkoevtmfr.supabase.co"; + const cleanPath = path.startsWith('/') ? path.slice(1) : path; + const separator = cleanPath.includes('?') ? '&' : '?'; + return `${baseUrl}/storage/v1/object/avatars/${cleanPath}${separator}t=${new Date().getTime()}`; + }; + useEffect(() => { + if (user?.id) { + const loadData = async () => { + try { + // 1. Busca dados médicos (Tabela Patients) + const patientDetails = await patientsService.getById(user.id); + + // 2. Busca dados de sistema frescos (Tabela Profiles via getMe) + // Isso garante que pegamos o avatar real do banco, não do cache local + const userSystemData = await usersService.getMe(); + + const freshAvatarPath = userSystemData?.profile?.avatar_url; + const freshAvatarUrl = buildAvatarUrl(freshAvatarPath); + + setPatientData({ + name: patientDetails.full_name || user.name, + email: user.email, + phone: patientDetails.phone_mobile || "", + cpf: patientDetails.cpf || "", + birthDate: patientDetails.birth_date || "", + cep: patientDetails.cep || "", + street: patientDetails.street || "", + number: patientDetails.number || "", + city: patientDetails.city || "", + avatarFullUrl: freshAvatarUrl, // Usa a URL fresca do banco + }); + } catch (error) { + console.error("Erro ao buscar detalhes:", error); + toast({ + title: "Erro", + description: "Não foi possível carregar seus dados completos.", + variant: "destructive", + }); + } + }; + loadData(); + } + }, [user?.id, user?.email, user?.name]); // Removi user.avatarFullUrl para não depender do cache const handleInputChange = ( field: keyof PatientProfileData, @@ -85,6 +104,27 @@ export default function PatientProfile() { setPatientData((prev) => (prev ? { ...prev, [field]: value } : null)); }; + const updateLocalSession = (updates: { full_name?: string; avatar_url?: string }) => { + try { + const storedUserString = localStorage.getItem("user_info"); + if (storedUserString) { + const storedUser = JSON.parse(storedUserString); + + if (!storedUser.user_metadata) storedUser.user_metadata = {}; + if (updates.full_name) storedUser.user_metadata.full_name = updates.full_name; + if (updates.avatar_url) storedUser.user_metadata.avatar_url = updates.avatar_url; + + if (!storedUser.profile) storedUser.profile = {}; + if (updates.full_name) storedUser.profile.full_name = updates.full_name; + if (updates.avatar_url) storedUser.profile.avatar_url = updates.avatar_url; + + localStorage.setItem("user_info", JSON.stringify(storedUser)); + } + } catch (e) { + console.error("Erro ao atualizar sessão local:", e); + } + }; + const handleSave = async () => { if (!patientData || !user) return; setIsSaving(true); @@ -99,12 +139,22 @@ export default function PatientProfile() { number: patientData.number, city: patientData.city, }; + await patientsService.update(user.id, patientPayload); + await api.patch(`/rest/v1/profiles?id=eq.${user.id}`, { + full_name: patientData.name, + }); + + updateLocalSession({ full_name: patientData.name }); + toast({ title: "Sucesso!", - description: "Seus dados foram atualizados.", + description: "Seus dados foram atualizados. A página será recarregada.", }); + setIsEditing(false); + setTimeout(() => window.location.reload(), 1000); + } catch (error) { console.error("Erro ao salvar dados:", error); toast({ @@ -128,9 +178,6 @@ export default function PatientProfile() { if (!file || !user) return; const fileExt = file.name.split(".").pop(); - - // *** A CORREÇÃO ESTÁ AQUI *** - // O caminho salvo no banco de dados não deve conter o nome do bucket. const filePath = `${user.id}/avatar.${fileExt}`; try { @@ -139,15 +186,21 @@ export default function PatientProfile() { avatar_url: filePath, }); - const newFullUrl = `https://yuanqfswhberkoevtmfr.supabase.co/storage/v1/object/public/avatars/${filePath}?t=${new Date().getTime()}`; + const newFullUrl = buildAvatarUrl(filePath); + setPatientData((prev) => prev ? { ...prev, avatarFullUrl: newFullUrl } : null ); + updateLocalSession({ avatar_url: filePath }); + toast({ title: "Sucesso!", description: "Sua foto de perfil foi atualizada.", }); + + setTimeout(() => window.location.reload(), 1000); + } catch (error) { console.error("Erro no upload do avatar:", error); toast({ @@ -161,7 +214,9 @@ export default function PatientProfile() { if (isAuthLoading || !patientData) { return ( -
Carregando seus dados...
+
+

Carregando seus dados...

+
); } @@ -320,22 +375,20 @@ export default function PatientProfile() {
-
+
- - - {patientData.name - .split(" ") - .map((n) => n[0]) - .join("")} + + + {getInitials(patientData.name)}
@@ -344,12 +397,12 @@ export default function PatientProfile() { ref={fileInputRef} onChange={handleAvatarUpload} className="hidden" - accept="image/png, image/jpeg" + accept="image/png, image/jpeg, image/jpg" />
-

{patientData.name}

-

Paciente

+

{patientData.name}

+

Paciente

@@ -380,4 +433,4 @@ export default function PatientProfile() {
); -} +} \ No newline at end of file diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx index 6e80e29..9742de4 100644 --- a/components/Sidebar.tsx +++ b/components/Sidebar.tsx @@ -6,6 +6,7 @@ import { useRouter, usePathname } from "next/navigation"; import Link from "next/link"; import Cookies from "js-cookie"; import { api } from "@/services/api.mjs"; +import { usersService } from "@/services/usersApi.mjs"; // Importando usersService import { useAccessibility } from "@/app/context/AccessibilityContext"; import { Button } from "@/components/ui/button"; @@ -46,6 +47,7 @@ interface UserData { full_name: string; phone_mobile: string; role: string; + avatar_url?: string; }; identities: { identity_id: string; @@ -71,8 +73,18 @@ export default function Sidebar({ children }: SidebarProps) { const [role, setRole] = useState(); const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [showLogoutDialog, setShowLogoutDialog] = useState(false); + const [avatarFullUrl, setAvatarFullUrl] = useState(undefined); const router = useRouter(); const pathname = usePathname(); + + // Função auxiliar para construir URL + const buildAvatarUrl = (path: string) => { + if (!path) return undefined; + const baseUrl = "https://yuanqfswhberkoevtmfr.supabase.co"; + const cleanPath = path.startsWith('/') ? path.slice(1) : path; + const separator = cleanPath.includes('?') ? '&' : '?'; + return `${baseUrl}/storage/v1/object/avatars/${cleanPath}${separator}t=${new Date().getTime()}`; + }; const { theme, contrast } = useAccessibility(); useEffect(() => { @@ -80,31 +92,82 @@ export default function Sidebar({ children }: SidebarProps) { const token = localStorage.getItem("token"); if (userInfoString && token) { - const userInfo = JSON.parse(userInfoString); + try { + const userInfo = JSON.parse(userInfoString); + + // 1. Tenta pegar o avatar do cache local + let rawAvatarPath = + userInfo.profile?.avatar_url || + userInfo.user_metadata?.avatar_url || + userInfo.app_metadata?.avatar_url || + ""; + + // Configura estado inicial com o que tem no cache + setUserData({ + id: userInfo.id ?? "", + email: userInfo.email ?? "", + app_metadata: { + user_role: userInfo.app_metadata?.user_role ?? "patient", + }, + user_metadata: { + cpf: userInfo.user_metadata?.cpf ?? "", + email_verified: userInfo.user_metadata?.email_verified ?? false, + full_name: userInfo.user_metadata?.full_name || userInfo.profile?.full_name || "Usuário", + phone_mobile: userInfo.user_metadata?.phone_mobile ?? "", + role: userInfo.user_metadata?.role ?? "", + avatar_url: rawAvatarPath, + }, + identities: userInfo.identities ?? [], + is_anonymous: userInfo.is_anonymous ?? false, + }); + + setRole(userInfo.user_metadata?.role); + + if (rawAvatarPath) { + setAvatarFullUrl(buildAvatarUrl(rawAvatarPath)); + } + + // 2. AUTO-REPARO: Se não tiver avatar ou profile no cache, busca na API e atualiza + if (!rawAvatarPath || !userInfo.profile) { + console.log("[Sidebar] Cache incompleto. Buscando dados frescos..."); + usersService.getMe().then((freshData) => { + if (freshData && freshData.profile) { + const freshAvatar = freshData.profile.avatar_url; + + // Atualiza o objeto local + const updatedUserInfo = { + ...userInfo, + profile: freshData.profile, // Injeta o profile completo + user_metadata: { + ...userInfo.user_metadata, + avatar_url: freshAvatar || userInfo.user_metadata.avatar_url + } + }; + + // Salva no localStorage para a próxima vez + localStorage.setItem("user_info", JSON.stringify(updatedUserInfo)); + console.log("[Sidebar] LocalStorage sincronizado com sucesso."); + + // Atualiza visualmente se achou um avatar novo + if (freshAvatar && freshAvatar !== rawAvatarPath) { + setAvatarFullUrl(buildAvatarUrl(freshAvatar)); + // Atualiza o userData também para refletir no tooltip + setUserData(prev => prev ? ({ + ...prev, + user_metadata: { + ...prev.user_metadata, + avatar_url: freshAvatar + } + }) : undefined); + } + } + }).catch(err => console.error("[Sidebar] Falha no auto-reparo:", err)); + } + + } catch (e) { + console.error("Erro ao processar dados do usuário na Sidebar:", e); + } - setUserData({ - id: userInfo.id ?? "", - email: userInfo.email ?? "", - app_metadata: { - user_role: userInfo.app_metadata?.user_role ?? "patient", - }, - user_metadata: { - cpf: userInfo.user_metadata?.cpf ?? "", - email_verified: userInfo.user_metadata?.email_verified ?? false, - full_name: userInfo.user_metadata?.full_name ?? "", - phone_mobile: userInfo.user_metadata?.phone_mobile ?? "", - role: userInfo.user_metadata?.role ?? "", - }, - identities: - userInfo.identities?.map((identity: any) => ({ - identity_id: identity.identity_id ?? "", - id: identity.id ?? "", - user_id: identity.user_id ?? "", - provider: identity.provider ?? "", - })) ?? [], - is_anonymous: userInfo.is_anonymous ?? false, - }); - setRole(userInfo.user_metadata?.role); } else { router.push("/login"); } @@ -129,6 +192,7 @@ export default function Sidebar({ children }: SidebarProps) { try { await api.logout(); } catch (error) { + console.error("Erro ao fazer logout", error); } finally { localStorage.removeItem("user_info"); localStorage.removeItem("token"); @@ -303,6 +367,7 @@ export default function Sidebar({ children }: SidebarProps) { sidebarCollapsed={sidebarCollapsed} handleLogout={handleLogout} isActive={role !== "paciente"} + avatarUrl={avatarFullUrl} />
diff --git a/components/ui/userToolTip.tsx b/components/ui/userToolTip.tsx index f1f3d54..9b6fde7 100644 --- a/components/ui/userToolTip.tsx +++ b/components/ui/userToolTip.tsx @@ -33,6 +33,7 @@ interface Props { sidebarCollapsed: boolean; handleLogout: () => void; isActive: boolean; + avatarUrl?: string; } export default function SidebarUserSection({ @@ -40,6 +41,7 @@ export default function SidebarUserSection({ sidebarCollapsed, handleLogout, isActive, + avatarUrl, }: Props) { const pathname = usePathname(); const menuItems: any[] = [ @@ -56,6 +58,18 @@ export default function SidebarUserSection({ { href: "/patient/reports", icon: ClipboardPlus, label: "Meus Laudos" }, { href: "/patient/profile", icon: SquareUser, label: "Meus Dados" }, ]; + + // Função auxiliar para obter iniciais + const getInitials = (name: string) => { + if (!name) return "U"; + return name + .split(" ") + .map((n) => n[0]) + .slice(0, 2) + .join("") + .toUpperCase(); + }; + return (
{/* POPUP DE INFORMAÇÕES DO USUÁRIO */} @@ -67,12 +81,13 @@ export default function SidebarUserSection({ }`} > - - - {userData.user_metadata.full_name - .split(" ") - .map((n) => n[0]) - .join("")} + + + {getInitials(userData.user_metadata.full_name)}