diff --git a/app/manager/usuario/novo/page.tsx b/app/manager/usuario/novo/page.tsx index e4176c0..4166d53 100644 --- a/app/manager/usuario/novo/page.tsx +++ b/app/manager/usuario/novo/page.tsx @@ -114,7 +114,7 @@ export default function NovoUsuarioPage() { password: formData.senha, full_name: formData.nomeCompleto, phone: formData.telefone || null, - role: formData.papel, + roles: [formData.papel, "paciente"], cpf: formData.cpf, create_patient_record: isPatient, phone_mobile: isPatient ? formData.telefone || null : undefined, @@ -126,7 +126,7 @@ export default function NovoUsuarioPage() { console.error("Erro ao criar usuário:", e); // 3. MENSAGEM DE ERRO MELHORADA const detail = e.message?.split('detail:"')[1]?.split('"')[0] || e.message; - setError(detail.replace(/\\/g, '') || "Não foi possível criar o usuário. Verifique os dados e tente novamente."); + setError(detail.replace(/\\/g, "") || "Não foi possível criar o usuário. Verifique os dados e tente novamente."); } finally { setIsSaving(false); } @@ -238,4 +238,4 @@ export default function NovoUsuarioPage() { ); -} \ No newline at end of file +} diff --git a/app/patient/profile/page.tsx b/app/patient/profile/page.tsx index a81db2a..6b42c07 100644 --- a/app/patient/profile/page.tsx +++ b/app/patient/profile/page.tsx @@ -3,7 +3,7 @@ "use client"; import { useState, useEffect, useRef } from "react"; -import Sidebar from "@/components/Sidebar" +import Sidebar from "@/components/Sidebar"; import { useAuthLayout } from "@/hooks/useAuthLayout"; import { patientsService } from "@/services/patientsApi.mjs"; import { api } from "@/services/api.mjs"; @@ -18,186 +18,243 @@ import { toast } from "@/hooks/use-toast"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; interface PatientProfileData { - name: string; - email: string; - phone: string; - cpf: string; - birthDate: string; - cep: string; - street: string; - number: string; - city: string; - avatarFullUrl?: string; + name: string; + email: string; + phone: string; + cpf: string; + birthDate: string; + cep: string; + street: string; + number: string; + city: string; + avatarFullUrl?: string; } export default function PatientProfile() { - const { user, isLoading: isAuthLoading } = useAuthLayout({ requiredRole: 'patient' }); - const [patientData, setPatientData] = useState(null); - const [isEditing, setIsEditing] = useState(false); - const [isSaving, setIsSaving] = useState(false); - const fileInputRef = useRef(null); + const { user, isLoading: isAuthLoading } = useAuthLayout({ requiredRole: ["paciente", "admin", "medico", "gestor", "secretaria"] }); + const [patientData, setPatientData] = useState(null); + const [isEditing, setIsEditing] = useState(false); + const [isSaving, setIsSaving] = useState(false); + const fileInputRef = useRef(null); - useEffect(() => { - if (user?.id) { - const fetchPatientDetails = async () => { - try { - const patientDetails = await patientsService.getById(user.id); - 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" }); + useEffect(() => { + if (user?.id) { + const fetchPatientDetails = async () => { + try { + const patientDetails = await patientsService.getById(user.id); + 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" }); + } + }; + fetchPatientDetails(); } - }; - fetchPatientDetails(); + }, [user]); + + const handleInputChange = (field: keyof PatientProfileData, value: string) => { + setPatientData((prev) => (prev ? { ...prev, [field]: value } : null)); + }; + + const handleSave = async () => { + if (!patientData || !user) return; + setIsSaving(true); + try { + const patientPayload = { + full_name: patientData.name, + cpf: patientData.cpf, + birth_date: patientData.birthDate, + phone_mobile: patientData.phone, + cep: patientData.cep, + street: patientData.street, + number: patientData.number, + city: patientData.city, + }; + await patientsService.update(user.id, patientPayload); + toast({ title: "Sucesso!", description: "Seus dados foram atualizados." }); + setIsEditing(false); + } catch (error) { + console.error("Erro ao salvar dados:", error); + toast({ title: "Erro", description: "Não foi possível salvar suas alterações.", variant: "destructive" }); + } finally { + setIsSaving(false); + } + }; + + const handleAvatarClick = () => { + fileInputRef.current?.click(); + }; + + const handleAvatarUpload = async (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + 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 { + await api.storage.upload("avatars", filePath, file); + await api.patch(`/rest/v1/profiles?id=eq.${user.id}`, { avatar_url: filePath }); + + const newFullUrl = `https://yuanqfswhberkoevtmfr.supabase.co/storage/v1/object/public/avatars/${filePath}?t=${new Date().getTime()}`; + setPatientData((prev) => (prev ? { ...prev, avatarFullUrl: newFullUrl } : null)); + + toast({ title: "Sucesso!", description: "Sua foto de perfil foi atualizada." }); + } catch (error) { + console.error("Erro no upload do avatar:", error); + toast({ title: "Erro de Upload", description: "Não foi possível enviar sua foto.", variant: "destructive" }); + } + }; + + if (isAuthLoading || !patientData) { + return ( + +
Carregando seus dados...
+
+ ); } - }, [user]); - const handleInputChange = (field: keyof PatientProfileData, value: string) => { - setPatientData((prev) => (prev ? { ...prev, [field]: value } : null)); - }; - - const handleSave = async () => { - if (!patientData || !user) return; - setIsSaving(true); - try { - const patientPayload = { - full_name: patientData.name, - cpf: patientData.cpf, - birth_date: patientData.birthDate, - phone_mobile: patientData.phone, - cep: patientData.cep, - street: patientData.street, - number: patientData.number, - city: patientData.city, - }; - await patientsService.update(user.id, patientPayload); - toast({ title: "Sucesso!", description: "Seus dados foram atualizados." }); - setIsEditing(false); - } catch (error) { - console.error("Erro ao salvar dados:", error); - toast({ title: "Erro", description: "Não foi possível salvar suas alterações.", variant: "destructive" }); - } finally { - setIsSaving(false); - } - }; - - const handleAvatarClick = () => { - fileInputRef.current?.click(); - }; - - const handleAvatarUpload = async (event: React.ChangeEvent) => { - const file = event.target.files?.[0]; - 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 { - await api.storage.upload('avatars', filePath, file); - await api.patch(`/rest/v1/profiles?id=eq.${user.id}`, { avatar_url: filePath }); - - const newFullUrl = `https://yuanqfswhberkoevtmfr.supabase.co/storage/v1/object/public/avatars/${filePath}?t=${new Date().getTime()}`; - setPatientData(prev => prev ? { ...prev, avatarFullUrl: newFullUrl } : null); - - toast({ title: "Sucesso!", description: "Sua foto de perfil foi atualizada." }); - } catch (error) { - console.error("Erro no upload do avatar:", error); - toast({ title: "Erro de Upload", description: "Não foi possível enviar sua foto.", variant: "destructive" }); - } - }; - - if (isAuthLoading || !patientData) { - return
Carregando seus dados...
; - } - - return ( - -
-
-
-

Meus Dados

-

Gerencie suas informações pessoais

-
- -
- -
-
- - Informações Pessoais - -
-
handleInputChange("name", e.target.value)} disabled={!isEditing} />
-
handleInputChange("cpf", e.target.value)} disabled={!isEditing} />
-
-
handleInputChange("birthDate", e.target.value)} disabled={!isEditing} />
-
-
- - Contato e Endereço - -
-
-
handleInputChange("phone", e.target.value)} disabled={!isEditing} />
-
-
-
handleInputChange("cep", e.target.value)} disabled={!isEditing} />
-
handleInputChange("street", e.target.value)} disabled={!isEditing} />
-
-
-
handleInputChange("number", e.target.value)} disabled={!isEditing} />
-
handleInputChange("city", e.target.value)} disabled={!isEditing} />
-
-
-
-
- -
- - Resumo do Perfil - -
-
- - - {patientData.name.split(" ").map((n) => n[0]).join("")} - -
- + return ( + +
+
+
+

Meus Dados

+

Gerencie suas informações pessoais

- -
-
-

{patientData.name}

-

Paciente

-
+
-
-
{patientData.email}
-
{patientData.phone || "Não informado"}
-
{patientData.birthDate ? new Date(patientData.birthDate).toLocaleDateString("pt-BR", { timeZone: 'UTC' }) : "Não informado"}
+ +
+
+ + + + + Informações Pessoais + + + +
+
+ + handleInputChange("name", e.target.value)} disabled={!isEditing} /> +
+
+ + handleInputChange("cpf", e.target.value)} disabled={!isEditing} /> +
+
+
+ + handleInputChange("birthDate", e.target.value)} disabled={!isEditing} /> +
+
+
+ + + + + Contato e Endereço + + + +
+
+ + +
+
+ + handleInputChange("phone", e.target.value)} disabled={!isEditing} /> +
+
+
+
+ + handleInputChange("cep", e.target.value)} disabled={!isEditing} /> +
+
+ + handleInputChange("street", e.target.value)} disabled={!isEditing} /> +
+
+
+
+ + handleInputChange("number", e.target.value)} disabled={!isEditing} /> +
+
+ + handleInputChange("city", e.target.value)} disabled={!isEditing} /> +
+
+
+
+
+ +
+ + + Resumo do Perfil + + +
+
+ + + + {patientData.name + .split(" ") + .map((n) => n[0]) + .join("")} + + +
+ +
+ +
+
+

{patientData.name}

+

Paciente

+
+
+
+
+ + {patientData.email} +
+
+ + {patientData.phone || "Não informado"} +
+
+ + {patientData.birthDate ? new Date(patientData.birthDate).toLocaleDateString("pt-BR", { timeZone: "UTC" }) : "Não informado"} +
+
+
+
+
- - -
-
-
- - ) -} \ No newline at end of file +
+ + ); +} diff --git a/hooks/useAuthLayout.ts b/hooks/useAuthLayout.ts index 23f6e31..ad0d4ed 100644 --- a/hooks/useAuthLayout.ts +++ b/hooks/useAuthLayout.ts @@ -1,75 +1,68 @@ // ARQUIVO COMPLETO PARA: hooks/useAuthLayout.ts -import { useState, useEffect } from 'react'; -import { useRouter } from 'next/navigation'; -import { usersService } from '@/services/usersApi.mjs'; +import { useState, useEffect } from "react"; +import { useRouter } from "next/navigation"; +import { usersService } from "@/services/usersApi.mjs"; import { toast } from "@/hooks/use-toast"; interface UserLayoutData { - id: string; - name: string; - email: string; - roles: string[]; - avatar_url?: string; - avatarFullUrl?: string; + id: string; + name: string; + email: string; + roles: string[]; + avatar_url?: string; + avatarFullUrl?: string; } interface UseAuthLayoutOptions { - requiredRole?: string; + requiredRole?: string[]; } export function useAuthLayout({ requiredRole }: UseAuthLayoutOptions = {}) { - const [user, setUser] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const router = useRouter(); + const [user, setUser] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const router = useRouter(); - useEffect(() => { - const fetchUserData = async () => { - try { - const fullUserData = await usersService.getMe(); + useEffect(() => { + const fetchUserData = async () => { + try { + const fullUserData = await usersService.getMe(); - if ( - requiredRole && - !fullUserData.roles.includes(requiredRole) && - !fullUserData.roles.includes('admin') - ) { - console.error(`Acesso negado. Requer perfil '${requiredRole}', mas o usuário tem '${fullUserData.roles.join(', ')}'.`); - toast({ - title: "Acesso Negado", - description: "Você não tem permissão para acessar esta página.", - variant: "destructive", - }); - router.push('/'); - return; - } + if (!fullUserData.roles.some((role) => requiredRole?.includes(role))) { + console.error(`Acesso negado. Requer perfil '${requiredRole}', mas o usuário tem '${fullUserData.roles.join(", ")}'.`); + toast({ + title: "Acesso Negado", + description: "Você não tem permissão para acessar esta página.", + variant: "destructive", + }); + router.push("/"); + return; + } - const avatarPath = fullUserData.profile.avatar_url; - - // *** A CORREÇÃO ESTÁ AQUI *** - // Adicionamos o nome do bucket 'avatars' na URL final. - const avatarFullUrl = avatarPath - ? `https://yuanqfswhberkoevtmfr.supabase.co/storage/v1/object/public/avatars/${avatarPath}` - : undefined; + const avatarPath = fullUserData.profile.avatar_url; - setUser({ - id: fullUserData.user.id, - name: fullUserData.profile.full_name || 'Usuário', - email: fullUserData.user.email, - roles: fullUserData.roles, - avatar_url: avatarPath, - avatarFullUrl: avatarFullUrl, - }); + // *** A CORREÇÃO ESTÁ AQUI *** + // Adicionamos o nome do bucket 'avatars' na URL final. + const avatarFullUrl = avatarPath ? `https://yuanqfswhberkoevtmfr.supabase.co/storage/v1/object/public/avatars/${avatarPath}` : undefined; - } catch (error) { - console.error("Falha na autenticação do layout:", error); - router.push("/login"); - } finally { - setIsLoading(false); - } - }; + setUser({ + id: fullUserData.user.id, + name: fullUserData.profile.full_name || "Usuário", + email: fullUserData.user.email, + roles: fullUserData.roles, + avatar_url: avatarPath, + avatarFullUrl: avatarFullUrl, + }); + } catch (error) { + console.error("Falha na autenticação do layout:", error); + router.push("/login"); + } finally { + setIsLoading(false); + } + }; - fetchUserData(); - }, [router, requiredRole]); + fetchUserData(); + }, [router, requiredRole]); - return { user, isLoading }; -} \ No newline at end of file + return { user, isLoading }; +}