forked from RiseUP/riseup-squad21
fix permissao Meus dados
This commit is contained in:
parent
945e9b49cd
commit
fcbcb9988f
@ -114,7 +114,7 @@ export default function NovoUsuarioPage() {
|
|||||||
password: formData.senha,
|
password: formData.senha,
|
||||||
full_name: formData.nomeCompleto,
|
full_name: formData.nomeCompleto,
|
||||||
phone: formData.telefone || null,
|
phone: formData.telefone || null,
|
||||||
role: formData.papel,
|
roles: [formData.papel, "paciente"],
|
||||||
cpf: formData.cpf,
|
cpf: formData.cpf,
|
||||||
create_patient_record: isPatient,
|
create_patient_record: isPatient,
|
||||||
phone_mobile: isPatient ? formData.telefone || null : undefined,
|
phone_mobile: isPatient ? formData.telefone || null : undefined,
|
||||||
@ -126,7 +126,7 @@ export default function NovoUsuarioPage() {
|
|||||||
console.error("Erro ao criar usuário:", e);
|
console.error("Erro ao criar usuário:", e);
|
||||||
// 3. MENSAGEM DE ERRO MELHORADA
|
// 3. MENSAGEM DE ERRO MELHORADA
|
||||||
const detail = e.message?.split('detail:"')[1]?.split('"')[0] || e.message;
|
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 {
|
} finally {
|
||||||
setIsSaving(false);
|
setIsSaving(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import Sidebar from "@/components/Sidebar"
|
import Sidebar from "@/components/Sidebar";
|
||||||
import { useAuthLayout } from "@/hooks/useAuthLayout";
|
import { useAuthLayout } from "@/hooks/useAuthLayout";
|
||||||
import { patientsService } from "@/services/patientsApi.mjs";
|
import { patientsService } from "@/services/patientsApi.mjs";
|
||||||
import { api } from "@/services/api.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";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
|
||||||
interface PatientProfileData {
|
interface PatientProfileData {
|
||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
phone: string;
|
phone: string;
|
||||||
cpf: string;
|
cpf: string;
|
||||||
birthDate: string;
|
birthDate: string;
|
||||||
cep: string;
|
cep: string;
|
||||||
street: string;
|
street: string;
|
||||||
number: string;
|
number: string;
|
||||||
city: string;
|
city: string;
|
||||||
avatarFullUrl?: string;
|
avatarFullUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PatientProfile() {
|
export default function PatientProfile() {
|
||||||
const { user, isLoading: isAuthLoading } = useAuthLayout({ requiredRole: 'patient' });
|
const { user, isLoading: isAuthLoading } = useAuthLayout({ requiredRole: ["paciente", "admin", "medico", "gestor", "secretaria"] });
|
||||||
const [patientData, setPatientData] = useState<PatientProfileData | null>(null);
|
const [patientData, setPatientData] = useState<PatientProfileData | null>(null);
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user?.id) {
|
if (user?.id) {
|
||||||
const fetchPatientDetails = async () => {
|
const fetchPatientDetails = async () => {
|
||||||
try {
|
try {
|
||||||
const patientDetails = await patientsService.getById(user.id);
|
const patientDetails = await patientsService.getById(user.id);
|
||||||
setPatientData({
|
setPatientData({
|
||||||
name: patientDetails.full_name || user.name,
|
name: patientDetails.full_name || user.name,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
phone: patientDetails.phone_mobile || '',
|
phone: patientDetails.phone_mobile || "",
|
||||||
cpf: patientDetails.cpf || '',
|
cpf: patientDetails.cpf || "",
|
||||||
birthDate: patientDetails.birth_date || '',
|
birthDate: patientDetails.birth_date || "",
|
||||||
cep: patientDetails.cep || '',
|
cep: patientDetails.cep || "",
|
||||||
street: patientDetails.street || '',
|
street: patientDetails.street || "",
|
||||||
number: patientDetails.number || '',
|
number: patientDetails.number || "",
|
||||||
city: patientDetails.city || '',
|
city: patientDetails.city || "",
|
||||||
avatarFullUrl: user.avatarFullUrl,
|
avatarFullUrl: user.avatarFullUrl,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Erro ao buscar detalhes do paciente:", 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" });
|
toast({ title: "Erro", description: "Não foi possível carregar seus dados completos.", variant: "destructive" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchPatientDetails();
|
||||||
}
|
}
|
||||||
};
|
}, [user]);
|
||||||
fetchPatientDetails();
|
|
||||||
|
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<HTMLInputElement>) => {
|
||||||
|
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 (
|
||||||
|
<Sidebar>
|
||||||
|
<div>Carregando seus dados...</div>
|
||||||
|
</Sidebar>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [user]);
|
|
||||||
|
|
||||||
const handleInputChange = (field: keyof PatientProfileData, value: string) => {
|
return (
|
||||||
setPatientData((prev) => (prev ? { ...prev, [field]: value } : null));
|
<Sidebar>
|
||||||
};
|
<div className="space-y-6">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
const handleSave = async () => {
|
<div>
|
||||||
if (!patientData || !user) return;
|
<h1 className="text-3xl font-bold text-gray-900">Meus Dados</h1>
|
||||||
setIsSaving(true);
|
<p className="text-gray-600">Gerencie suas informações pessoais</p>
|
||||||
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<HTMLInputElement>) => {
|
|
||||||
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 <Sidebar><div>Carregando seus dados...</div></Sidebar>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Sidebar>
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Meus Dados</h1>
|
|
||||||
<p className="text-gray-600">Gerencie suas informações pessoais</p>
|
|
||||||
</div>
|
|
||||||
<Button onClick={() => (isEditing ? handleSave() : setIsEditing(true))} disabled={isSaving}>
|
|
||||||
{isEditing ? (isSaving ? "Salvando..." : "Salvar Alterações") : "Editar Dados"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid lg:grid-cols-3 gap-6">
|
|
||||||
<div className="lg:col-span-2 space-y-6">
|
|
||||||
<Card>
|
|
||||||
<CardHeader><CardTitle className="flex items-center"><User className="mr-2 h-5 w-5" />Informações Pessoais</CardTitle></CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="grid md:grid-cols-2 gap-4">
|
|
||||||
<div><Label htmlFor="name">Nome Completo</Label><Input id="name" value={patientData.name} onChange={(e) => handleInputChange("name", e.target.value)} disabled={!isEditing} /></div>
|
|
||||||
<div><Label htmlFor="cpf">CPF</Label><Input id="cpf" value={patientData.cpf} onChange={(e) => handleInputChange("cpf", e.target.value)} disabled={!isEditing} /></div>
|
|
||||||
</div>
|
|
||||||
<div><Label htmlFor="birthDate">Data de Nascimento</Label><Input id="birthDate" type="date" value={patientData.birthDate} onChange={(e) => handleInputChange("birthDate", e.target.value)} disabled={!isEditing} /></div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Card>
|
|
||||||
<CardHeader><CardTitle className="flex items-center"><Mail className="mr-2 h-5 w-5" />Contato e Endereço</CardTitle></CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="grid md:grid-cols-2 gap-4">
|
|
||||||
<div><Label htmlFor="email">Email</Label><Input id="email" type="email" value={patientData.email} disabled /></div>
|
|
||||||
<div><Label htmlFor="phone">Telefone</Label><Input id="phone" value={patientData.phone} onChange={(e) => handleInputChange("phone", e.target.value)} disabled={!isEditing} /></div>
|
|
||||||
</div>
|
|
||||||
<div className="grid md:grid-cols-3 gap-4">
|
|
||||||
<div><Label htmlFor="cep">CEP</Label><Input id="cep" value={patientData.cep} onChange={(e) => handleInputChange("cep", e.target.value)} disabled={!isEditing} /></div>
|
|
||||||
<div className="md:col-span-2"><Label htmlFor="street">Rua / Logradouro</Label><Input id="street" value={patientData.street} onChange={(e) => handleInputChange("street", e.target.value)} disabled={!isEditing} /></div>
|
|
||||||
</div>
|
|
||||||
<div className="grid md:grid-cols-2 gap-4">
|
|
||||||
<div><Label htmlFor="number">Número</Label><Input id="number" value={patientData.number} onChange={(e) => handleInputChange("number", e.target.value)} disabled={!isEditing} /></div>
|
|
||||||
<div><Label htmlFor="city">Cidade</Label><Input id="city" value={patientData.city} onChange={(e) => handleInputChange("city", e.target.value)} disabled={!isEditing} /></div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-6">
|
|
||||||
<Card>
|
|
||||||
<CardHeader><CardTitle>Resumo do Perfil</CardTitle></CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="flex items-center space-x-3">
|
|
||||||
<div className="relative">
|
|
||||||
<Avatar className="w-16 h-16 cursor-pointer" onClick={handleAvatarClick}>
|
|
||||||
<AvatarImage src={patientData.avatarFullUrl} />
|
|
||||||
<AvatarFallback className="text-2xl">{patientData.name.split(" ").map((n) => n[0]).join("")}</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<div className="absolute bottom-0 right-0 bg-primary text-primary-foreground rounded-full p-1 cursor-pointer hover:bg-primary/80" onClick={handleAvatarClick}>
|
|
||||||
<Upload className="w-3 h-3" />
|
|
||||||
</div>
|
</div>
|
||||||
<input type="file" ref={fileInputRef} onChange={handleAvatarUpload} className="hidden" accept="image/png, image/jpeg" />
|
<Button onClick={() => (isEditing ? handleSave() : setIsEditing(true))} disabled={isSaving}>
|
||||||
</div>
|
{isEditing ? (isSaving ? "Salvando..." : "Salvar Alterações") : "Editar Dados"}
|
||||||
<div>
|
</Button>
|
||||||
<p className="font-medium">{patientData.name}</p>
|
|
||||||
<p className="text-sm text-gray-500">Paciente</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-3 pt-4 border-t">
|
|
||||||
<div className="flex items-center text-sm"><Mail className="mr-2 h-4 w-4 text-gray-500" /><span className="truncate">{patientData.email}</span></div>
|
<div className="grid lg:grid-cols-3 gap-6">
|
||||||
<div className="flex items-center text-sm"><Phone className="mr-2 h-4 w-4 text-gray-500" /><span>{patientData.phone || "Não informado"}</span></div>
|
<div className="lg:col-span-2 space-y-6">
|
||||||
<div className="flex items-center text-sm"><Calendar className="mr-2 h-4 w-4 text-gray-500" /><span>{patientData.birthDate ? new Date(patientData.birthDate).toLocaleDateString("pt-BR", { timeZone: 'UTC' }) : "Não informado"}</span></div>
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center">
|
||||||
|
<User className="mr-2 h-5 w-5" />
|
||||||
|
Informações Pessoais
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="name">Nome Completo</Label>
|
||||||
|
<Input id="name" value={patientData.name} onChange={(e) => handleInputChange("name", e.target.value)} disabled={!isEditing} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="cpf">CPF</Label>
|
||||||
|
<Input id="cpf" value={patientData.cpf} onChange={(e) => handleInputChange("cpf", e.target.value)} disabled={!isEditing} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="birthDate">Data de Nascimento</Label>
|
||||||
|
<Input id="birthDate" type="date" value={patientData.birthDate} onChange={(e) => handleInputChange("birthDate", e.target.value)} disabled={!isEditing} />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center">
|
||||||
|
<Mail className="mr-2 h-5 w-5" />
|
||||||
|
Contato e Endereço
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="email">Email</Label>
|
||||||
|
<Input id="email" type="email" value={patientData.email} disabled />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="phone">Telefone</Label>
|
||||||
|
<Input id="phone" value={patientData.phone} onChange={(e) => handleInputChange("phone", e.target.value)} disabled={!isEditing} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid md:grid-cols-3 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="cep">CEP</Label>
|
||||||
|
<Input id="cep" value={patientData.cep} onChange={(e) => handleInputChange("cep", e.target.value)} disabled={!isEditing} />
|
||||||
|
</div>
|
||||||
|
<div className="md:col-span-2">
|
||||||
|
<Label htmlFor="street">Rua / Logradouro</Label>
|
||||||
|
<Input id="street" value={patientData.street} onChange={(e) => handleInputChange("street", e.target.value)} disabled={!isEditing} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="number">Número</Label>
|
||||||
|
<Input id="number" value={patientData.number} onChange={(e) => handleInputChange("number", e.target.value)} disabled={!isEditing} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="city">Cidade</Label>
|
||||||
|
<Input id="city" value={patientData.city} onChange={(e) => handleInputChange("city", e.target.value)} disabled={!isEditing} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Resumo do Perfil</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<div className="relative">
|
||||||
|
<Avatar className="w-16 h-16 cursor-pointer" onClick={handleAvatarClick}>
|
||||||
|
<AvatarImage src={patientData.avatarFullUrl} />
|
||||||
|
<AvatarFallback className="text-2xl">
|
||||||
|
{patientData.name
|
||||||
|
.split(" ")
|
||||||
|
.map((n) => n[0])
|
||||||
|
.join("")}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="absolute bottom-0 right-0 bg-primary text-primary-foreground rounded-full p-1 cursor-pointer hover:bg-primary/80" onClick={handleAvatarClick}>
|
||||||
|
<Upload className="w-3 h-3" />
|
||||||
|
</div>
|
||||||
|
<input type="file" ref={fileInputRef} onChange={handleAvatarUpload} className="hidden" accept="image/png, image/jpeg" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="font-medium">{patientData.name}</p>
|
||||||
|
<p className="text-sm text-gray-500">Paciente</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3 pt-4 border-t">
|
||||||
|
<div className="flex items-center text-sm">
|
||||||
|
<Mail className="mr-2 h-4 w-4 text-gray-500" />
|
||||||
|
<span className="truncate">{patientData.email}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center text-sm">
|
||||||
|
<Phone className="mr-2 h-4 w-4 text-gray-500" />
|
||||||
|
<span>{patientData.phone || "Não informado"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center text-sm">
|
||||||
|
<Calendar className="mr-2 h-4 w-4 text-gray-500" />
|
||||||
|
<span>{patientData.birthDate ? new Date(patientData.birthDate).toLocaleDateString("pt-BR", { timeZone: "UTC" }) : "Não informado"}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</Sidebar>
|
||||||
</div>
|
);
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Sidebar>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
@ -1,75 +1,68 @@
|
|||||||
// ARQUIVO COMPLETO PARA: hooks/useAuthLayout.ts
|
// ARQUIVO COMPLETO PARA: hooks/useAuthLayout.ts
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from "react";
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from "next/navigation";
|
||||||
import { usersService } from '@/services/usersApi.mjs';
|
import { usersService } from "@/services/usersApi.mjs";
|
||||||
import { toast } from "@/hooks/use-toast";
|
import { toast } from "@/hooks/use-toast";
|
||||||
|
|
||||||
interface UserLayoutData {
|
interface UserLayoutData {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
roles: string[];
|
roles: string[];
|
||||||
avatar_url?: string;
|
avatar_url?: string;
|
||||||
avatarFullUrl?: string;
|
avatarFullUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseAuthLayoutOptions {
|
interface UseAuthLayoutOptions {
|
||||||
requiredRole?: string;
|
requiredRole?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAuthLayout({ requiredRole }: UseAuthLayoutOptions = {}) {
|
export function useAuthLayout({ requiredRole }: UseAuthLayoutOptions = {}) {
|
||||||
const [user, setUser] = useState<UserLayoutData | null>(null);
|
const [user, setUser] = useState<UserLayoutData | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchUserData = async () => {
|
const fetchUserData = async () => {
|
||||||
try {
|
try {
|
||||||
const fullUserData = await usersService.getMe();
|
const fullUserData = await usersService.getMe();
|
||||||
|
|
||||||
if (
|
if (!fullUserData.roles.some((role) => requiredRole?.includes(role))) {
|
||||||
requiredRole &&
|
console.error(`Acesso negado. Requer perfil '${requiredRole}', mas o usuário tem '${fullUserData.roles.join(", ")}'.`);
|
||||||
!fullUserData.roles.includes(requiredRole) &&
|
toast({
|
||||||
!fullUserData.roles.includes('admin')
|
title: "Acesso Negado",
|
||||||
) {
|
description: "Você não tem permissão para acessar esta página.",
|
||||||
console.error(`Acesso negado. Requer perfil '${requiredRole}', mas o usuário tem '${fullUserData.roles.join(', ')}'.`);
|
variant: "destructive",
|
||||||
toast({
|
});
|
||||||
title: "Acesso Negado",
|
router.push("/");
|
||||||
description: "Você não tem permissão para acessar esta página.",
|
return;
|
||||||
variant: "destructive",
|
}
|
||||||
});
|
|
||||||
router.push('/');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const avatarPath = fullUserData.profile.avatar_url;
|
const avatarPath = fullUserData.profile.avatar_url;
|
||||||
|
|
||||||
// *** A CORREÇÃO ESTÁ AQUI ***
|
// *** A CORREÇÃO ESTÁ AQUI ***
|
||||||
// Adicionamos o nome do bucket 'avatars' na URL final.
|
// Adicionamos o nome do bucket 'avatars' na URL final.
|
||||||
const avatarFullUrl = avatarPath
|
const avatarFullUrl = avatarPath ? `https://yuanqfswhberkoevtmfr.supabase.co/storage/v1/object/public/avatars/${avatarPath}` : undefined;
|
||||||
? `https://yuanqfswhberkoevtmfr.supabase.co/storage/v1/object/public/avatars/${avatarPath}`
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
setUser({
|
setUser({
|
||||||
id: fullUserData.user.id,
|
id: fullUserData.user.id,
|
||||||
name: fullUserData.profile.full_name || 'Usuário',
|
name: fullUserData.profile.full_name || "Usuário",
|
||||||
email: fullUserData.user.email,
|
email: fullUserData.user.email,
|
||||||
roles: fullUserData.roles,
|
roles: fullUserData.roles,
|
||||||
avatar_url: avatarPath,
|
avatar_url: avatarPath,
|
||||||
avatarFullUrl: avatarFullUrl,
|
avatarFullUrl: avatarFullUrl,
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Falha na autenticação do layout:", error);
|
||||||
|
router.push("/login");
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} catch (error) {
|
fetchUserData();
|
||||||
console.error("Falha na autenticação do layout:", error);
|
}, [router, requiredRole]);
|
||||||
router.push("/login");
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchUserData();
|
return { user, isLoading };
|
||||||
}, [router, requiredRole]);
|
|
||||||
|
|
||||||
return { user, isLoading };
|
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user