commit
48b0c409ea
@ -4,7 +4,6 @@
|
||||
|
||||
import type React from "react";
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import DoctorLayout from "@/components/doctor-layout";
|
||||
import { useAuthLayout } from "@/hooks/useAuthLayout";
|
||||
import { appointmentsService } from "@/services/appointmentsApi.mjs";
|
||||
import { patientsService } from "@/services/patientsApi.mjs";
|
||||
@ -19,6 +18,7 @@ import { Clock, Calendar as CalendarIcon, User, X, RefreshCw, Loader2, MapPin, P
|
||||
import { format, isFuture, parseISO, isValid, isToday, isTomorrow } from "date-fns";
|
||||
import { ptBR } from "date-fns/locale";
|
||||
import { toast } from "sonner";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
// Interfaces (sem alteração)
|
||||
interface EnrichedAppointment {
|
||||
@ -129,11 +129,11 @@ export default function DoctorAppointmentsPage() {
|
||||
};
|
||||
|
||||
if (isAuthLoading) {
|
||||
return <DoctorLayout><div>Carregando...</div></DoctorLayout>;
|
||||
return <Sidebar><div>Carregando...</div></Sidebar>;
|
||||
}
|
||||
|
||||
return (
|
||||
<DoctorLayout>
|
||||
<Sidebar>
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-foreground">Agenda Médica</h1>
|
||||
@ -224,6 +224,6 @@ export default function DoctorAppointmentsPage() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DoctorLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import DoctorLayout from "@/components/doctor-layout";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Calendar, Clock, User, Trash2 } from "lucide-react";
|
||||
@ -14,6 +13,7 @@ import { AvailabilityService } from "@/services/availabilityApi.mjs";
|
||||
import { exceptionsService } from "@/services/exceptionApi.mjs";
|
||||
import { doctorsService } from "@/services/doctorsApi.mjs";
|
||||
import { usersService } from "@/services/usersApi.mjs";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
type Availability = {
|
||||
id: string;
|
||||
@ -231,7 +231,7 @@ export default function PatientDashboard() {
|
||||
}, [availability]);
|
||||
|
||||
return (
|
||||
<DoctorLayout>
|
||||
<Sidebar>
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">Dashboard</h1>
|
||||
@ -409,6 +409,6 @@ export default function PatientDashboard() {
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</DoctorLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
@ -3,14 +3,12 @@
|
||||
import type React from "react";
|
||||
import Link from "next/link";
|
||||
import { useState, useEffect } from "react";
|
||||
import DoctorLayout from "@/components/doctor-layout";
|
||||
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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Clock, Calendar as CalendarIcon, MapPin, Phone, User, X, RefreshCw } from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Calendar as CalendarIcon, RefreshCw } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import { exceptionsService } from "@/services/exceptionApi.mjs";
|
||||
@ -19,6 +17,7 @@ import { exceptionsService } from "@/services/exceptionApi.mjs";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { format } from "date-fns"; // Usaremos o date-fns para formatação e comparação de datas
|
||||
import { doctorsService } from "@/services/doctorsApi.mjs";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
type Doctor = {
|
||||
id: string;
|
||||
@ -147,7 +146,7 @@ export default function ExceptionPage() {
|
||||
const displayDate = selectedCalendarDate ? new Date(selectedCalendarDate).toLocaleDateString("pt-BR", { weekday: "long", day: "2-digit", month: "long" }) : "Selecione uma data";
|
||||
|
||||
return (
|
||||
<DoctorLayout>
|
||||
<Sidebar>
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">Adicione exceções</h1>
|
||||
@ -254,6 +253,6 @@ export default function ExceptionPage() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DoctorLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
@ -7,7 +7,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import DoctorLayout from "@/components/doctor-layout";
|
||||
|
||||
import { AvailabilityService } from "@/services/availabilityApi.mjs";
|
||||
import { usersService } from "@/services/usersApi.mjs";
|
||||
@ -17,9 +16,10 @@ import { toast } from "@/hooks/use-toast";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||
import { Eye, Edit, Calendar, Trash2 } from "lucide-react";
|
||||
import { Edit, Trash2 } from "lucide-react";
|
||||
import { AvailabilityEditModal } from "@/components/ui/availability-edit-modal";
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
// ... (Interfaces de tipo omitidas para brevidade, pois não foram alteradas)
|
||||
|
||||
@ -323,7 +323,7 @@ export default function AvailabilityPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<DoctorLayout>
|
||||
<Sidebar>
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
@ -506,6 +506,6 @@ export default function AvailabilityPage() {
|
||||
onSubmit={handleEdit}
|
||||
/>
|
||||
|
||||
</DoctorLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
@ -12,7 +12,7 @@ import { Textarea } from "@/components/ui/textarea";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { ArrowLeft, Save } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import DoctorLayout from "@/components/doctor-layout";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
// Mock data - in a real app, this would come from an API
|
||||
const mockDoctors = [
|
||||
@ -124,7 +124,7 @@ export default function EditarMedicoPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<DoctorLayout>
|
||||
<Sidebar>
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<Link href="/medicos">
|
||||
@ -512,6 +512,6 @@ export default function EditarMedicoPage() {
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</DoctorLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useState, useEffect } from "react";
|
||||
import DoctorLayout from "@/components/doctor-layout";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@ -17,6 +16,7 @@ import { format } from "date-fns";
|
||||
import TiptapEditor from "@/components/ui/tiptap-editor";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { reportsApi } from "@/services/reportsApi.mjs";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
export default function EditarLaudoPage() {
|
||||
const router = useRouter();
|
||||
@ -108,7 +108,7 @@ export default function EditarLaudoPage() {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<DoctorLayout>
|
||||
<Sidebar>
|
||||
<div className="container mx-auto p-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@ -130,12 +130,12 @@ export default function EditarLaudoPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</DoctorLayout>
|
||||
</Sidebar>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<DoctorLayout>
|
||||
<Sidebar>
|
||||
<div className="container mx-auto p-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@ -232,6 +232,6 @@ export default function EditarLaudoPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</DoctorLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
@ -17,7 +17,7 @@ import { format } from "date-fns";
|
||||
import TiptapEditor from "@/components/ui/tiptap-editor";
|
||||
|
||||
import { reportsApi } from "@/services/reportsApi.mjs";
|
||||
import DoctorLayout from "@/components/doctor-layout";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
|
||||
|
||||
@ -97,7 +97,7 @@ export default function NovoLaudoPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<DoctorLayout>
|
||||
<Sidebar>
|
||||
<div className="container mx-auto p-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@ -189,6 +189,6 @@ export default function NovoLaudoPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</DoctorLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
@ -8,7 +8,7 @@ import Link from 'next/link';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { api } from '@/services/api.mjs';
|
||||
import { reportsApi } from '@/services/reportsApi.mjs';
|
||||
import DoctorLayout from '@/components/doctor-layout';
|
||||
import Sidebar from '@/components/Sidebar';
|
||||
|
||||
export default function LaudosPage() {
|
||||
const [patient, setPatient] = useState(null);
|
||||
@ -49,7 +49,7 @@ export default function LaudosPage() {
|
||||
const paginate = (pageNumber) => setCurrentPage(pageNumber);
|
||||
|
||||
return (
|
||||
<DoctorLayout>
|
||||
<Sidebar>
|
||||
<div className="container mx-auto p-4">
|
||||
{loading ? (
|
||||
<p>Carregando...</p>
|
||||
@ -123,6 +123,6 @@ export default function LaudosPage() {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</DoctorLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
@ -9,7 +9,7 @@ import { Textarea } from "@/components/ui/textarea";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Upload, Plus, X, ChevronDown } from "lucide-react";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||
import DoctorLayout from "@/components/doctor-layout";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
export default function NovoMedicoPage() {
|
||||
const [anexosOpen, setAnexosOpen] = useState(false);
|
||||
@ -24,7 +24,7 @@ export default function NovoMedicoPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<DoctorLayout>
|
||||
<Sidebar>
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
@ -466,6 +466,6 @@ export default function NovoMedicoPage() {
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</DoctorLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,25 +2,14 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
import DoctorLayout from "@/components/doctor-layout";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||
import { Eye, Edit, Calendar, Trash2, Loader2 } from "lucide-react";
|
||||
import { api } from "@/services/api.mjs";
|
||||
import { PatientDetailsModal } from "@/components/ui/patient-details-modal";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
interface Paciente {
|
||||
id: string;
|
||||
@ -171,7 +160,7 @@ export default function PacientesPage() {
|
||||
}, [fetchPacientes]);
|
||||
|
||||
return (
|
||||
<DoctorLayout>
|
||||
<Sidebar>
|
||||
<div className="space-y-6 px-2 sm:px-4 md:px-6">
|
||||
{/* Cabeçalho */}
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
@ -363,6 +352,6 @@ export default function PacientesPage() {
|
||||
isOpen={isModalOpen}
|
||||
onClose={handleCloseModal}
|
||||
/>
|
||||
</DoctorLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import FinancierLayout from "@/components/finance-layout";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
interface Paciente {
|
||||
id: string;
|
||||
@ -14,43 +14,10 @@ interface Paciente {
|
||||
}
|
||||
|
||||
export default function PacientesPage() {
|
||||
const [pacientes, setPacientes] = useState<Paciente[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchPacientes() {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const res = await fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes");
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const json = await res.json();
|
||||
const items = Array.isArray(json?.data) ? json.data : [];
|
||||
|
||||
const mapped = items.map((p: any) => ({
|
||||
id: String(p.id ?? ""),
|
||||
nome: p.nome ?? "",
|
||||
telefone: p?.contato?.celular ?? p?.contato?.telefone1 ?? p?.telefone ?? "",
|
||||
cidade: p?.endereco?.cidade ?? p?.cidade ?? "",
|
||||
estado: p?.endereco?.estado ?? p?.estado ?? "",
|
||||
ultimoAtendimento: p.ultimo_atendimento ?? p.ultimoAtendimento ?? "",
|
||||
proximoAtendimento: p.proximo_atendimento ?? p.proximoAtendimento ?? "",
|
||||
}));
|
||||
|
||||
setPacientes(mapped);
|
||||
} catch (e: any) {
|
||||
setError(e?.message || "Erro ao carregar pacientes");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
fetchPacientes();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<FinancierLayout>
|
||||
<Sidebar>
|
||||
<div></div>
|
||||
</FinancierLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,82 +1,247 @@
|
||||
// Caminho: app/login/page.tsx
|
||||
|
||||
|
||||
"use client";
|
||||
|
||||
|
||||
import {usersService} from "@/services/usersApi.mjs";
|
||||
import { LoginForm } from "@/components/LoginForm";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ArrowLeft } from "lucide-react"; // Importa o ícone de seta
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { ArrowLeft, X } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import RenderFromTemplateContext from "next/dist/client/components/render-from-template-context";
|
||||
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div className="min-h-screen grid grid-cols-1 lg:grid-cols-2">
|
||||
|
||||
{/* PAINEL ESQUERDO: O Formulário */}
|
||||
<div className="relative flex flex-col items-center justify-center p-8 bg-background">
|
||||
|
||||
{/* Link para Voltar */}
|
||||
<div className="absolute top-8 left-8">
|
||||
<Link href="/" className="inline-flex items-center text-muted-foreground hover:text-primary transition-colors font-medium">
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
Voltar à página inicial
|
||||
</Link>
|
||||
</div>
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [email, setEmail] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);
|
||||
|
||||
{/* O contêiner principal que agora terá a sombra e o estilo de card */}
|
||||
<div className="w-full max-w-md bg-card p-10 rounded-2xl shadow-xl">
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-3xl font-bold text-foreground">Acesse sua conta</h1>
|
||||
<p className="text-muted-foreground mt-2">Bem-vindo(a) de volta ao MedConnect!</p>
|
||||
|
||||
const handleOpenModal = () => {
|
||||
// Tenta pegar o email do input do formulário de login
|
||||
const emailInput = document.querySelector('input[type="email"]') as HTMLInputElement;
|
||||
if (emailInput?.value) {
|
||||
setEmail(emailInput.value);
|
||||
}
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
|
||||
const handleResetPassword = async () => {
|
||||
if (!email.trim()) {
|
||||
setMessage({ type: "error", text: "Por favor, insira um e-mail válido." });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
setIsLoading(true);
|
||||
setMessage(null);
|
||||
|
||||
|
||||
try {
|
||||
// Chama o método que já faz o fetch corretamente
|
||||
const data = await usersService.resetPassword(email);
|
||||
|
||||
|
||||
console.log("Resposta resetPassword:", data);
|
||||
|
||||
|
||||
setMessage({
|
||||
type: "success",
|
||||
text: "E-mail de recuperação enviado! Verifique sua caixa de entrada.",
|
||||
});
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
setIsModalOpen(false);
|
||||
setMessage(null);
|
||||
setEmail("");
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
console.error("Erro no reset de senha:", error);
|
||||
setMessage({
|
||||
type: "error",
|
||||
text:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Erro ao enviar e-mail. Tente novamente.",
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const closeModal = () => {
|
||||
setIsModalOpen(false);
|
||||
setMessage(null);
|
||||
setEmail("");
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="min-h-screen grid grid-cols-1 lg:grid-cols-2">
|
||||
|
||||
{/* PAINEL ESQUERDO: O Formulário */}
|
||||
<div className="relative flex flex-col items-center justify-center p-8 bg-background">
|
||||
|
||||
{/* Link para Voltar */}
|
||||
<div className="absolute top-8 left-8">
|
||||
<Link href="/" className="inline-flex items-center text-muted-foreground hover:text-primary transition-colors font-medium">
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
Voltar à página inicial
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<LoginForm>
|
||||
{/* Children para o LoginForm */}
|
||||
<div className="mt-4 text-center text-sm">
|
||||
<Link href="/esqueci-minha-senha">
|
||||
<span className="text-muted-foreground hover:text-primary cursor-pointer underline">
|
||||
|
||||
{/* O contêiner principal que agora terá a sombra e o estilo de card */}
|
||||
<div className="w-full max-w-md bg-card p-10 rounded-2xl shadow-xl">
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-3xl font-bold text-foreground">Acesse sua conta</h1>
|
||||
<p className="text-muted-foreground mt-2">Bem-vindo(a) de volta ao MedConnect!</p>
|
||||
</div>
|
||||
|
||||
|
||||
<LoginForm>
|
||||
{/* Children para o LoginForm */}
|
||||
<div className="mt-4 text-center text-sm">
|
||||
<button
|
||||
onClick={handleOpenModal}
|
||||
className="text-muted-foreground hover:text-primary cursor-pointer underline bg-transparent border-none"
|
||||
>
|
||||
Esqueceu sua senha?
|
||||
</button>
|
||||
</div>
|
||||
</LoginForm>
|
||||
|
||||
|
||||
<div className="mt-6 text-center text-sm">
|
||||
<span className="text-muted-foreground">Não tem uma conta de paciente? </span>
|
||||
<Link href="/patient/register">
|
||||
<span className="font-semibold text-primary hover:underline cursor-pointer">
|
||||
Crie uma agora
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</LoginForm>
|
||||
|
||||
<div className="mt-6 text-center text-sm">
|
||||
<span className="text-muted-foreground">Não tem uma conta de paciente? </span>
|
||||
<Link href="/patient/register">
|
||||
<span className="font-semibold text-primary hover:underline cursor-pointer">
|
||||
Crie uma agora
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* PAINEL DIREITO: A Imagem e Branding */}
|
||||
<div className="hidden lg:block relative">
|
||||
{/* Usamos o componente <Image> para otimização e performance */}
|
||||
<Image
|
||||
src="https://images.unsplash.com/photo-1576091160550-2173dba999ef?q=80&w=2070" // Uma imagem profissional de alta qualidade
|
||||
alt="Médica utilizando um tablet na clínica MedConnect"
|
||||
fill
|
||||
style={{ objectFit: 'cover' }}
|
||||
priority // Ajuda a carregar a imagem mais rápido
|
||||
/>
|
||||
{/* Camada de sobreposição para escurecer a imagem e destacar o texto */}
|
||||
<div className="absolute inset-0 bg-primary/80 flex flex-col items-start justify-end p-12 text-left">
|
||||
|
||||
{/* PAINEL DIREITO: A Imagem e Branding */}
|
||||
<div className="hidden lg:block relative">
|
||||
{/* Usamos o componente <Image> para otimização e performance */}
|
||||
<Image
|
||||
src="https://images.unsplash.com/photo-1576091160550-2173dba999ef?q=80&w=2070"
|
||||
alt="Médica utilizando um tablet na clínica MedConnect"
|
||||
fill
|
||||
style={{ objectFit: 'cover' }}
|
||||
priority
|
||||
/>
|
||||
{/* Camada de sobreposição para escurecer a imagem e destacar o texto */}
|
||||
<div className="absolute inset-0 bg-primary/80 flex flex-col items-start justify-end p-12 text-left">
|
||||
{/* BLOCO DE NOME ADICIONADO */}
|
||||
<div className="mb-6 border-l-4 border-primary-foreground pl-4">
|
||||
<h1 className="text-5xl font-extrabold text-primary-foreground tracking-wider">
|
||||
<h1 className="text-5xl font-extrabold text-primary-foreground tracking-wider">
|
||||
MedConnect
|
||||
</h1>
|
||||
</h1>
|
||||
</div>
|
||||
<h2 className="text-4xl font-bold text-primary-foreground leading-tight">
|
||||
Tecnologia e Cuidado a Serviço da Sua Saúde.
|
||||
Tecnologia e Cuidado a Serviço da Sua Saúde.
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-primary-foreground/80">
|
||||
Acesse seu portal para uma experiência de saúde integrada, segura e eficiente.
|
||||
Acesse seu portal para uma experiência de saúde integrada, segura e eficiente.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Modal de Recuperação de Senha */}
|
||||
{isModalOpen && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
||||
<div className="relative w-full max-w-md bg-card p-8 rounded-2xl shadow-2xl mx-4">
|
||||
{/* Botão de fechar */}
|
||||
<button
|
||||
onClick={closeModal}
|
||||
className="absolute top-4 right-4 text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
|
||||
{/* Cabeçalho */}
|
||||
<div className="mb-6">
|
||||
<h2 className="text-2xl font-bold text-foreground">Recuperar Senha</h2>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
Insira seu e-mail e enviaremos um link para redefinir sua senha.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Input de e-mail */}
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-foreground mb-2">
|
||||
E-mail
|
||||
</label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="seu@email.com"
|
||||
disabled={isLoading}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Mensagem de feedback */}
|
||||
{message && (
|
||||
<div
|
||||
className={`p-3 rounded-lg text-sm ${
|
||||
message.type === "success"
|
||||
? "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300"
|
||||
: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300"
|
||||
}`}
|
||||
>
|
||||
{message.text}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* Botões */}
|
||||
<div className="flex gap-3 pt-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={closeModal}
|
||||
disabled={isLoading}
|
||||
className="flex-1"
|
||||
>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleResetPassword}
|
||||
disabled={isLoading}
|
||||
className="flex-1"
|
||||
>
|
||||
{isLoading ? "Enviando..." : "Resetar Senha"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import ManagerLayout from "@/components/manager-layout";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Calendar, Clock, Plus, User } from "lucide-react";
|
||||
@ -8,6 +7,7 @@ import Link from "next/link";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { usersService } from "services/usersApi.mjs";
|
||||
import { doctorsService } from "services/doctorsApi.mjs";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
export default function ManagerDashboard() {
|
||||
// 🔹 Estados para usuários
|
||||
@ -55,7 +55,7 @@ export default function ManagerDashboard() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ManagerLayout>
|
||||
<Sidebar>
|
||||
<div className="space-y-6">
|
||||
{/* Cabeçalho */}
|
||||
<div>
|
||||
@ -185,6 +185,6 @@ export default function ManagerDashboard() {
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</ManagerLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import { Textarea } from "@/components/ui/textarea"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { Save, Loader2, ArrowLeft } from "lucide-react"
|
||||
import ManagerLayout from "@/components/manager-layout"
|
||||
import Sidebar from "@/components/Sidebar"
|
||||
import { doctorsService } from "services/doctorsApi.mjs";
|
||||
|
||||
const UF_LIST = ["AC", "AL", "AP", "AM", "BA", "CE", "DF", "ES", "GO", "MA", "MT", "MS", "MG", "PA", "PB", "PR", "PE", "PI", "RJ", "RN", "RS", "RO", "RR", "SC", "SP", "SE", "TO"];
|
||||
@ -207,17 +207,17 @@ export default function EditarMedicoPage() {
|
||||
};
|
||||
if (loading) {
|
||||
return (
|
||||
<ManagerLayout>
|
||||
<Sidebar>
|
||||
<div className="flex justify-center items-center h-full w-full py-16">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-green-600" />
|
||||
<p className="ml-2 text-gray-600">Carregando dados do médico...</p>
|
||||
</div>
|
||||
</ManagerLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ManagerLayout>
|
||||
<Sidebar>
|
||||
<div className="w-full space-y-6 p-4 md:p-8">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
@ -487,6 +487,6 @@ export default function EditarMedicoPage() {
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ManagerLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
@ -1,25 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useState, useCallback, useMemo } from "react"
|
||||
import ManagerLayout from "@/components/manager-layout";
|
||||
import Link from "next/link"
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Plus, Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react"
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog"
|
||||
import { Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react"
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"
|
||||
|
||||
import { doctorsService } from "services/doctorsApi.mjs";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
|
||||
interface Doctor {
|
||||
@ -193,7 +184,7 @@ export default function DoctorsPage() {
|
||||
|
||||
|
||||
return (
|
||||
<ManagerLayout>
|
||||
<Sidebar>
|
||||
<div className="space-y-6 px-2 sm:px-4 md:px-6">
|
||||
|
||||
{/* Cabeçalho */}
|
||||
@ -430,6 +421,6 @@ export default function DoctorsPage() {
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</ManagerLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
@ -13,9 +13,8 @@ import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { ArrowLeft, Save, Trash2, Paperclip, Upload } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import SecretaryLayout from "@/components/secretary-layout";
|
||||
import { patientsService } from "@/services/patientsApi.mjs";
|
||||
import { json } from "stream/consumers";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
export default function EditarPacientePage() {
|
||||
const router = useRouter();
|
||||
@ -247,7 +246,7 @@ export default function EditarPacientePage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<SecretaryLayout>
|
||||
<Sidebar>
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<Link href="/manager/pacientes">
|
||||
@ -677,6 +676,6 @@ export default function EditarPacientePage() {
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</SecretaryLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
@ -6,10 +6,10 @@ import Link from "next/link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Plus, Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react";
|
||||
import { Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react";
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
|
||||
import { patientsService } from "@/services/patientsApi.mjs";
|
||||
import ManagerLayout from "@/components/manager-layout";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
// Defina o tamanho da página.
|
||||
const PAGE_SIZE = 5;
|
||||
@ -145,7 +145,7 @@ export default function PacientesPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<ManagerLayout>
|
||||
<Sidebar>
|
||||
<div className="space-y-6 px-2 sm:px-4 md:px-8">
|
||||
{/* Header (Responsividade OK) */}
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
@ -449,6 +449,6 @@ export default function PacientesPage() {
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</ManagerLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
@ -8,7 +8,7 @@ import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Save, Loader2, ArrowLeft } from "lucide-react"
|
||||
import ManagerLayout from "@/components/manager-layout"
|
||||
import Sidebar from "@/components/Sidebar"
|
||||
|
||||
// Mock user service for demonstration. Replace with your actual API service.
|
||||
const usersService = {
|
||||
@ -155,17 +155,17 @@ export default function EditarUsuarioPage() {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ManagerLayout>
|
||||
<Sidebar>
|
||||
<div className="flex justify-center items-center h-full w-full py-16">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-green-600" />
|
||||
<p className="ml-2 text-gray-600">Carregando dados do usuário...</p>
|
||||
</div>
|
||||
</ManagerLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ManagerLayout>
|
||||
<Sidebar>
|
||||
<div className="w-full max-w-2xl mx-auto space-y-6 p-4 md:p-8">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
@ -274,6 +274,6 @@ export default function EditarUsuarioPage() {
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ManagerLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
@ -10,11 +10,11 @@ import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Save, Loader2 } from "lucide-react";
|
||||
import ManagerLayout from "@/components/manager-layout";
|
||||
import { usersService } from "@/services/usersApi.mjs";
|
||||
import { doctorsService } from "@/services/doctorsApi.mjs";
|
||||
import { login } from "services/api.mjs";
|
||||
import { isValidCPF } from "@/lib/utils"; // 1. IMPORTAÇÃO DA FUNÇÃO DE VALIDAÇÃO
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
interface UserFormData {
|
||||
email: string;
|
||||
@ -135,7 +135,7 @@ export default function NovoUsuarioPage() {
|
||||
const isMedico = formData.papel === "medico";
|
||||
|
||||
return (
|
||||
<ManagerLayout>
|
||||
<Sidebar>
|
||||
<div className="w-full h-full p-4 md:p-8 flex justify-center items-start">
|
||||
<div className="w-full max-w-screen-lg space-y-8">
|
||||
<div className="flex items-center justify-between border-b pb-4">
|
||||
@ -236,6 +236,6 @@ export default function NovoUsuarioPage() {
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</ManagerLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
@ -2,28 +2,14 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useState, useCallback } from "react";
|
||||
import ManagerLayout from "@/components/manager-layout";
|
||||
import Link from "next/link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Plus, Eye, Filter, Loader2 } from "lucide-react";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { AlertDialog, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
|
||||
import { api, login } from "services/api.mjs";
|
||||
import { usersService } from "services/usersApi.mjs";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
interface FlatUser {
|
||||
id: string;
|
||||
@ -192,7 +178,7 @@ export default function UsersPage() {
|
||||
|
||||
|
||||
return (
|
||||
<ManagerLayout>
|
||||
<Sidebar>
|
||||
<div className="space-y-6 px-2 sm:px-4 md:px-8">
|
||||
|
||||
{/* Header */}
|
||||
@ -424,6 +410,6 @@ export default function UsersPage() {
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</ManagerLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import PatientLayout from "@/components/patient-layout";
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@ -10,6 +9,7 @@ import { toast } from "sonner";
|
||||
|
||||
import { appointmentsService } from "@/services/appointmentsApi.mjs";
|
||||
import { usersService } from "@/services/usersApi.mjs";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
// Tipagem correta para o usuário
|
||||
interface UserProfile {
|
||||
@ -129,7 +129,7 @@ export default function PatientAppointmentsPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<PatientLayout>
|
||||
<Sidebar>
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
@ -185,6 +185,6 @@ export default function PatientAppointmentsPage() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</PatientLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import PatientLayout from "@/components/patient-layout"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Calendar, Clock, User, Plus } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import Sidebar from "@/components/Sidebar"
|
||||
|
||||
export default function PatientDashboard() {
|
||||
return (
|
||||
<PatientLayout>
|
||||
<Sidebar>
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">Dashboard</h1>
|
||||
@ -108,6 +108,6 @@ export default function PatientDashboard() {
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</PatientLayout>
|
||||
</Sidebar>
|
||||
)
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import PatientLayout from "@/components/patient-layout";
|
||||
import Sidebar from "@/components/Sidebar"
|
||||
import { useAuthLayout } from "@/hooks/useAuthLayout";
|
||||
import { patientsService } from "@/services/patientsApi.mjs";
|
||||
import { api } from "@/services/api.mjs";
|
||||
@ -121,11 +121,11 @@ export default function PatientProfile() {
|
||||
};
|
||||
|
||||
if (isAuthLoading || !patientData) {
|
||||
return <PatientLayout><div>Carregando seus dados...</div></PatientLayout>;
|
||||
return <Sidebar><div>Carregando seus dados...</div></Sidebar>;
|
||||
}
|
||||
|
||||
return (
|
||||
<PatientLayout>
|
||||
<Sidebar>
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
@ -198,6 +198,6 @@ export default function PatientProfile() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PatientLayout>
|
||||
);
|
||||
</Sidebar>
|
||||
)
|
||||
}
|
||||
@ -1,13 +1,13 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import PatientLayout from "@/components/patient-layout"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||
import { toast } from "@/hooks/use-toast"
|
||||
import { FileText, Download, Eye, Calendar, User, X } from "lucide-react"
|
||||
import Sidebar from "@/components/Sidebar"
|
||||
|
||||
interface Report {
|
||||
id: string
|
||||
@ -287,7 +287,7 @@ export default function ReportsPage() {
|
||||
const pendingReports = reports.filter((report) => report.status === "pendente")
|
||||
|
||||
return (
|
||||
<PatientLayout>
|
||||
<Sidebar>
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">Meus Laudos</h1>
|
||||
@ -536,6 +536,6 @@ export default function ReportsPage() {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</PatientLayout>
|
||||
</Sidebar>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
// app/patient/appointments/page.tsx
|
||||
import PatientLayout from "@/components/patient-layout";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
import ScheduleForm from "@/components/schedule/schedule-form";
|
||||
|
||||
|
||||
export default function PatientAppointments() {
|
||||
return (
|
||||
<PatientLayout>
|
||||
<Sidebar>
|
||||
<ScheduleForm />
|
||||
</PatientLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,20 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import SecretaryLayout from "@/components/secretary-layout";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Dialog } from "@/components/ui/dialog";
|
||||
import { Calendar, Clock, MapPin, Phone, User, Trash2, Pencil } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import Link from "next/link";
|
||||
import { appointmentsService } from "@/services/appointmentsApi.mjs";
|
||||
import { patientsService } from "@/services/patientsApi.mjs";
|
||||
import { doctorsService } from "@/services/doctorsApi.mjs";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
export default function SecretaryAppointments() {
|
||||
const [appointments, setAppointments] = useState<any[]>([]);
|
||||
@ -144,7 +141,7 @@ export default function SecretaryAppointments() {
|
||||
const appointmentStatuses = ["requested", "confirmed", "checked_in", "completed", "cancelled", "no_show"];
|
||||
|
||||
return (
|
||||
<SecretaryLayout>
|
||||
<Sidebar>
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
@ -225,6 +222,6 @@ export default function SecretaryAppointments() {
|
||||
<Dialog open={deleteModal} onOpenChange={setDeleteModal}>
|
||||
{/* ... (código do modal de deleção) ... */}
|
||||
</Dialog>
|
||||
</SecretaryLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
@ -1,10 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import SecretaryLayout from "@/components/secretary-layout";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
import { Card, CardContent, CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
@ -14,6 +10,7 @@ import Link from "next/link";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { patientsService } from "@/services/patientsApi.mjs";
|
||||
import { appointmentsService } from "@/services/appointmentsApi.mjs";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
export default function SecretaryDashboard() {
|
||||
// Estados
|
||||
@ -100,7 +97,7 @@ export default function SecretaryDashboard() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SecretaryLayout>
|
||||
<Sidebar>
|
||||
<div className="space-y-6">
|
||||
{/* Cabeçalho */}
|
||||
<div>
|
||||
@ -299,6 +296,6 @@ export default function SecretaryDashboard() {
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</SecretaryLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
@ -13,9 +13,8 @@ import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { ArrowLeft, Save, Trash2, Paperclip, Upload } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import SecretaryLayout from "@/components/secretary-layout";
|
||||
import { patientsService } from "@/services/patientsApi.mjs";
|
||||
import { json } from "stream/consumers";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
export default function EditarPacientePage() {
|
||||
const router = useRouter();
|
||||
@ -247,7 +246,7 @@ export default function EditarPacientePage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<SecretaryLayout>
|
||||
<Sidebar>
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<Link href="/secretary/pacientes">
|
||||
@ -677,6 +676,6 @@ export default function EditarPacientePage() {
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</SecretaryLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
// Caminho: app/(manager)/usuario/novo/page.tsx
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
@ -7,13 +6,9 @@ import Link from "next/link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
// O Select foi removido pois não é mais necessário
|
||||
import { Save, Loader2 } from "lucide-react";
|
||||
import ManagerLayout from "@/components/manager-layout";
|
||||
// Os imports originais foram mantidos, como solicitado
|
||||
import { usersService } from "services/usersApi.mjs";
|
||||
import { doctorsService } from "services/doctorsApi.mjs";
|
||||
import { login } from "services/api.mjs";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
// Interface simplificada para refletir apenas os campos necessários
|
||||
interface UserFormData {
|
||||
@ -97,7 +92,7 @@ export default function NovoUsuarioPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<ManagerLayout>
|
||||
<Sidebar>
|
||||
<div className="w-full h-full p-4 md:p-8 flex justify-center items-start">
|
||||
<div className="w-full max-w-screen-lg space-y-8">
|
||||
<div className="flex items-center justify-between border-b pb-4">
|
||||
@ -167,6 +162,6 @@ export default function NovoUsuarioPage() {
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</ManagerLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
@ -8,8 +8,8 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Plus, Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react";
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
|
||||
import SecretaryLayout from "@/components/secretary-layout";
|
||||
import { patientsService } from "@/services/patientsApi.mjs";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
// Defina o tamanho da página.
|
||||
const PAGE_SIZE = 5;
|
||||
@ -145,7 +145,7 @@ export default function PacientesPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<SecretaryLayout>
|
||||
<Sidebar>
|
||||
<div className="space-y-6 px-2 sm:px-4 md:px-8">
|
||||
{/* Header (Responsividade OK) */}
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
@ -457,6 +457,6 @@ export default function PacientesPage() {
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</SecretaryLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
import SecretaryLayout from "@/components/secretary-layout";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
import ScheduleForm from "@/components/schedule/schedule-form";
|
||||
|
||||
export default function SecretaryAppointments() {
|
||||
return (
|
||||
<SecretaryLayout>
|
||||
<Sidebar>
|
||||
<ScheduleForm />
|
||||
</SecretaryLayout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ import { Label } from "@/components/ui/label";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { Eye, EyeOff, Mail, Lock, Loader2 } from "lucide-react";
|
||||
import { usersService } from "@/services/usersApi.mjs";
|
||||
|
||||
interface LoginFormProps {
|
||||
children?: React.ReactNode;
|
||||
@ -31,8 +32,12 @@ export function LoginForm({ children }: LoginFormProps) {
|
||||
const { toast } = useToast();
|
||||
|
||||
const [userRoles, setUserRoles] = useState<string[]>([]);
|
||||
|
||||
// *** MUDANÇA 1: A função agora recebe o objeto 'user' como parâmetro ***
|
||||
const [authenticatedUser, setAuthenticatedUser] = useState<any>(null);
|
||||
|
||||
/**
|
||||
* --- NOVA FUNÇÃO ---
|
||||
* Finaliza o login com o perfil de dashboard escolhido e redireciona.
|
||||
*/
|
||||
const handleRoleSelection = (selectedDashboardRole: string, user: any) => {
|
||||
if (!user) {
|
||||
toast({ title: "Erro de Sessão", description: "Não foi possível encontrar os dados do usuário. Tente novamente.", variant: "destructive" });
|
||||
@ -47,12 +52,12 @@ export function LoginForm({ children }: LoginFormProps) {
|
||||
localStorage.setItem("user_info", JSON.stringify(completeUserInfo));
|
||||
|
||||
let redirectPath = "";
|
||||
switch (roleInLowerCase) {
|
||||
case "manager": redirectPath = "/manager/home"; break;
|
||||
case "doctor": redirectPath = "/doctor/medicos"; break;
|
||||
case "secretary": redirectPath = "/secretary/pacientes"; break;
|
||||
case "patient": redirectPath = "/patient/dashboard"; break;
|
||||
case "finance": redirectPath = "/finance/home"; break;
|
||||
switch (selectedDashboardRole) {
|
||||
case "gestor": redirectPath = "/manager/dashboard"; break;
|
||||
case "admin": redirectPath = "/manager/dashboard"; break;
|
||||
case "medico": redirectPath = "/doctor/dashboard"; break;
|
||||
case "secretaria": redirectPath = "/secretary/dashboard"; break;
|
||||
case "paciente": redirectPath = "/patient/dashboard"; break;
|
||||
}
|
||||
|
||||
if (redirectPath) {
|
||||
@ -77,55 +82,16 @@ export function LoginForm({ children }: LoginFormProps) {
|
||||
}
|
||||
|
||||
const rolesData = await api.get(`/rest/v1/user_roles?user_id=eq.${user.id}&select=role`);
|
||||
if (!rolesData || rolesData.length === 0) {
|
||||
|
||||
const me = await usersService.getMeSimple()
|
||||
console.log(me.roles)
|
||||
|
||||
if (!me.roles || me.roles.length === 0) {
|
||||
throw new Error("Nenhum perfil de acesso foi encontrado para este usuário.");
|
||||
}
|
||||
|
||||
const rolesFromApi: string[] = rolesData.map((r: any) => r.role);
|
||||
|
||||
// *** MUDANÇA 2: Passamos o objeto 'user' diretamente para a função de seleção ***
|
||||
const handleSelectionWithUser = (role: string) => handleRoleSelection(role, user);
|
||||
handleRoleSelection(me.roles[0], user);
|
||||
|
||||
if (rolesFromApi.includes("admin")) {
|
||||
const allRoles = ["manager", "doctor", "secretary", "patient", "finance"];
|
||||
setUserRoles(allRoles);
|
||||
// Atualizamos o onClick para usar a nova função que já tem o 'user'
|
||||
const roleButtons = allRoles.map((role) => (
|
||||
<Button key={role} variant="outline" className="h-11 text-base" onClick={() => handleSelectionWithUser(role)}>
|
||||
Entrar como: {role.charAt(0).toUpperCase() + role.slice(1)}
|
||||
</Button>
|
||||
));
|
||||
// Precisamos de um estado para renderizar os botões
|
||||
setRoleSelectionUI(roleButtons);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const displayRoles = new Set<string>();
|
||||
rolesFromApi.forEach((role) => {
|
||||
switch (role) {
|
||||
case "gestor": displayRoles.add("manager"); displayRoles.add("finance"); break;
|
||||
case "medico": displayRoles.add("doctor"); break;
|
||||
case "secretaria": displayRoles.add("secretary"); break;
|
||||
case "paciente": displayRoles.add("patient"); break;
|
||||
}
|
||||
});
|
||||
|
||||
const finalRoles = Array.from(displayRoles);
|
||||
|
||||
if (finalRoles.length === 1) {
|
||||
handleSelectionWithUser(finalRoles[0]);
|
||||
} else {
|
||||
setUserRoles(finalRoles);
|
||||
// Atualizamos o onClick aqui também
|
||||
const roleButtons = finalRoles.map((role) => (
|
||||
<Button key={role} variant="outline" className="h-11 text-base" onClick={() => handleSelectionWithUser(role)}>
|
||||
Entrar como: {role.charAt(0).toUpperCase() + role.slice(1)}
|
||||
</Button>
|
||||
));
|
||||
setRoleSelectionUI(roleButtons);
|
||||
setIsLoading(false);
|
||||
}
|
||||
} catch (error) {
|
||||
localStorage.removeItem("token");
|
||||
localStorage.removeItem("user_info");
|
||||
@ -172,7 +138,11 @@ export function LoginForm({ children }: LoginFormProps) {
|
||||
<h3 className="text-lg font-medium text-center text-foreground">Você tem múltiplos perfis</h3>
|
||||
<p className="text-sm text-muted-foreground text-center">Selecione com qual perfil deseja entrar:</p>
|
||||
<div className="flex flex-col space-y-3 pt-2">
|
||||
{roleSelectionUI}
|
||||
{userRoles.map((role) => (
|
||||
<Button key={role} variant="outline" className="h-11 text-base" onClick={() => handleRoleSelection(role, authenticatedUser)}>
|
||||
Entrar como: {role.charAt(0).toUpperCase() + role.slice(1)}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
291
components/Sidebar.tsx
Normal file
291
components/Sidebar.tsx
Normal file
@ -0,0 +1,291 @@
|
||||
// Caminho: [seu-caminho]/ManagerLayout.tsx
|
||||
"use client";
|
||||
|
||||
import type React from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import Cookies from "js-cookie"; // Mantido apenas para a limpeza de segurança no logout
|
||||
import { api } from "@/services/api.mjs";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Search, Bell, Calendar, User, LogOut, ChevronLeft, ChevronRight, Home, CalendarCheck2, ClipboardPlus, SquareUserRound, CalendarClock, Users, SquareUser, ClipboardList, Stethoscope, ClipboardMinus } from "lucide-react";
|
||||
import SidebarUserSection from "@/components/ui/userToolTip";
|
||||
|
||||
interface UserData {
|
||||
id: string;
|
||||
email: string;
|
||||
app_metadata: {
|
||||
user_role: string;
|
||||
};
|
||||
user_metadata: {
|
||||
cpf: string;
|
||||
email_verified: boolean;
|
||||
full_name: string;
|
||||
phone_mobile: string;
|
||||
role: string;
|
||||
};
|
||||
identities: {
|
||||
identity_id: string;
|
||||
id: string;
|
||||
user_id: string;
|
||||
provider: string;
|
||||
}[];
|
||||
is_anonymous: boolean;
|
||||
}
|
||||
|
||||
|
||||
interface MenuItem {
|
||||
href: string;
|
||||
icon: React.ElementType;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface SidebarProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function Sidebar({ children }: SidebarProps) {
|
||||
const [userData, setUserData] = useState<UserData>();
|
||||
const [role, setRole] = useState<string>();
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
const userInfoString = localStorage.getItem("user_info");
|
||||
// --- ALTERAÇÃO 1: Buscando o token no localStorage ---
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
if (userInfoString && token) {
|
||||
const userInfo = JSON.parse(userInfoString);
|
||||
|
||||
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 {
|
||||
// O redirecionamento para /login já estava correto. Ótimo!
|
||||
router.push("/login");
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
|
||||
const handleResize = () => {
|
||||
if (window.innerWidth < 1024) {
|
||||
setSidebarCollapsed(true);
|
||||
} else {
|
||||
setSidebarCollapsed(false);
|
||||
}
|
||||
};
|
||||
handleResize();
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, []);
|
||||
|
||||
const handleLogout = () => setShowLogoutDialog(true);
|
||||
|
||||
// --- ALTERAÇÃO 2: A função de logout agora é MUITO mais simples ---
|
||||
const confirmLogout = async () => {
|
||||
try {
|
||||
// Chama a função centralizada para fazer o logout no servidor
|
||||
await api.logout();
|
||||
} catch (error) {
|
||||
// O erro já é logado dentro da função api.logout, não precisamos fazer nada aqui
|
||||
} finally {
|
||||
// A responsabilidade do componente é apenas limpar o estado local e redirecionar
|
||||
localStorage.removeItem("user_info");
|
||||
localStorage.removeItem("token");
|
||||
Cookies.remove("access_token"); // Limpeza de segurança
|
||||
|
||||
setShowLogoutDialog(false);
|
||||
router.push("/"); // Redireciona para a home
|
||||
}
|
||||
};
|
||||
|
||||
const cancelLogout = () => setShowLogoutDialog(false);
|
||||
|
||||
const SetMenuItems = (role: any) => {
|
||||
const patientItems: MenuItem[] = [
|
||||
{ href: "/patient/dashboard", icon: Home, label: "Dashboard" },
|
||||
{ href: "/patient/schedule", icon: CalendarClock, label: "Agendar Consulta" },
|
||||
{ href: "/patient/appointments", icon: CalendarCheck2, label: "Minhas Consultas" },
|
||||
{ href: "/patient/reports", icon: ClipboardPlus, label: "Meus Laudos" },
|
||||
{ href: "/patient/profile", icon: SquareUser, label: "Meus Dados" },
|
||||
]
|
||||
|
||||
const doctorItems: MenuItem[] = [
|
||||
{ href: "/doctor/dashboard", icon: Home, label: "Dashboard" },
|
||||
{ href: "/doctor/medicos", icon: Users, label: "Gestão de Pacientes" },
|
||||
{ href: "/doctor/consultas", icon: CalendarCheck2, label: "Consultas" },
|
||||
{ href: "/doctor/disponibilidade", icon: ClipboardList, label: "Disponibilidade" },
|
||||
]
|
||||
|
||||
const secretaryItems: MenuItem[] = [
|
||||
{ href: "/secretary/dashboard", icon: Home, label: "Dashboard" },
|
||||
{ href: "/secretary/appointments", icon: CalendarCheck2, label: "Consultas" },
|
||||
{ href: "/secretary/schedule", icon: CalendarClock, label: "Agendar Consulta" },
|
||||
{ href: "/secretary/pacientes", icon: Users, label: "Gestão de Pacientes" },
|
||||
]
|
||||
|
||||
const managerItems: MenuItem[] = [
|
||||
{ href: "/manager/dashboard", icon: Home, label: "Dashboard" },
|
||||
{ href: "#", icon: ClipboardMinus, label: "Relatórios gerenciais" },
|
||||
{ href: "/manager/usuario", icon: Users, label: "Gestão de Usuários" },
|
||||
{ href: "/manager/home", icon: Stethoscope, label: "Gestão de Médicos" },
|
||||
{ href: "/manager/pacientes", icon: Users, label: "Gestão de Pacientes" },
|
||||
{ href: "/doctor/consultas", icon: CalendarCheck2, label: "Consultas" }, //adicionar botão de voltar pra pagina anterior
|
||||
]
|
||||
|
||||
let menuItems: MenuItem[];
|
||||
switch (role) {
|
||||
case "gestor":
|
||||
menuItems = managerItems;
|
||||
break;
|
||||
case "admin":
|
||||
menuItems = managerItems;
|
||||
break;
|
||||
case "medico":
|
||||
menuItems = doctorItems;
|
||||
break;
|
||||
case "secretaria":
|
||||
menuItems = secretaryItems;
|
||||
break;
|
||||
case "paciente":
|
||||
menuItems = patientItems;
|
||||
break;
|
||||
default:
|
||||
menuItems = patientItems;
|
||||
break;
|
||||
}
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
const menuItems = SetMenuItems(role)
|
||||
|
||||
if (!userData) {
|
||||
return (
|
||||
<div className="flex h-screen w-full items-center justify-center">
|
||||
Carregando...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex">
|
||||
<div
|
||||
className={`bg-white border-r border-gray-200 transition-all duration-300 fixed top-0 h-screen flex flex-col z-30 ${
|
||||
sidebarCollapsed ? "w-16" : "w-64"
|
||||
}`}
|
||||
>
|
||||
<div className="p-4 border-b border-gray-200 flex items-center justify-between">
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
|
||||
<div className="w-4 h-4 bg-white rounded-sm"></div>
|
||||
</div>
|
||||
<span className="font-semibold text-gray-900">MedConnect</span>
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
|
||||
className="p-1"
|
||||
>
|
||||
{sidebarCollapsed ? (
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
) : (
|
||||
<ChevronLeft className="w-4 h-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 p-2 overflow-y-auto">
|
||||
{menuItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive = pathname === item.href;
|
||||
return (
|
||||
<Link key={item.label} href={item.href}>
|
||||
<div
|
||||
className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${
|
||||
isActive
|
||||
? "bg-blue-50 text-blue-600 border-r-2 border-blue-600"
|
||||
: "text-gray-600 hover:bg-gray-50"
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-5 h-5 flex-shrink-0" />
|
||||
{!sidebarCollapsed && (
|
||||
<span className="font-medium">{item.label}</span>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<SidebarUserSection
|
||||
userData={userData}
|
||||
sidebarCollapsed={false}
|
||||
handleLogout={handleLogout}
|
||||
isActive={role === "paciente"? false: true}>
|
||||
</SidebarUserSection>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`flex-1 flex flex-col transition-all duration-300 w-full ${
|
||||
sidebarCollapsed ? "ml-16" : "ml-64"
|
||||
}`}
|
||||
>
|
||||
<header className="bg-gray-50 px-4 md:px-6 py-4 flex items-center justify-between"></header>
|
||||
<main className="flex-1 p-4 md:p-6">{children}</main>
|
||||
</div>
|
||||
|
||||
<Dialog open={showLogoutDialog} onOpenChange={setShowLogoutDialog}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Confirmar Saída</DialogTitle>
|
||||
<DialogDescription>
|
||||
Deseja realmente sair do sistema? Você precisará fazer login
|
||||
novamente para acessar sua conta.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="flex gap-2">
|
||||
<Button variant="outline" onClick={cancelLogout}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={confirmLogout}>
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
Sair
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,128 +0,0 @@
|
||||
// CÓDIGO REATORADO PARA: components/doctor-layout.tsx
|
||||
|
||||
"use client";
|
||||
|
||||
import type React from "react";
|
||||
import { useState } from "react";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { useAuthLayout } from "@/hooks/useAuthLayout"; // 1. Importamos nosso novo hook
|
||||
import { api } from "@/services/api.mjs";
|
||||
|
||||
// Componentes da UI
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Home, Calendar, Clock, User, LogOut, ChevronLeft, ChevronRight, Bell, FileText } from "lucide-react";
|
||||
import { Badge } from "./ui/badge";
|
||||
|
||||
export default function DoctorLayout({ children }: { children: React.ReactNode }) {
|
||||
// 2. Usamos o hook para buscar o usuário e controlar o acesso para 'medico'
|
||||
const { user, isLoading } = useAuthLayout({ requiredRole: 'medico' });
|
||||
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const confirmLogout = async () => {
|
||||
await api.logout();
|
||||
setShowLogoutDialog(false);
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
// ESTA PARTE É ÚNICA DE CADA LAYOUT E DEVE SER MANTIDA
|
||||
const menuItems = [
|
||||
{ href: "/doctor/dashboard", icon: Home, label: "Dashboard" },
|
||||
{ href: "/doctor/consultas", icon: Calendar, label: "Consultas" },
|
||||
{ href: "/doctor/medicos/editorlaudo", icon: Clock, label: "Editor de Laudo" },
|
||||
{ href: "/doctor/medicos", icon: User, label: "patientes" },
|
||||
{ href: "/doctor/disponibilidade", icon: Calendar, label: "Disponibilidade" },
|
||||
];
|
||||
|
||||
// 3. Adicionamos o estado de carregamento
|
||||
if (isLoading || !user) {
|
||||
return <div className="flex h-screen w-full items-center justify-center">Carregando...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex">
|
||||
<div className={`bg-white border-r border-gray-200 transition-all duration-300 fixed top-0 h-screen flex flex-col z-30 ${sidebarCollapsed ? "w-16" : "w-64"}`}>
|
||||
{/* Header da Sidebar */}
|
||||
<div className="p-4 border-b border-gray-200 flex items-center justify-between">
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center"><div className="w-4 h-4 bg-white rounded-sm"></div></div>
|
||||
<span className="font-semibold text-gray-900">MediConnect</span>
|
||||
</div>
|
||||
)}
|
||||
<Button variant="ghost" size="sm" onClick={() => setSidebarCollapsed(!sidebarCollapsed)} className="p-1">
|
||||
{sidebarCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Menu (específico deste layout) */}
|
||||
<nav className="flex-1 p-2 overflow-y-auto">
|
||||
{menuItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive = pathname === item.href || (item.href !== "/" && pathname.startsWith(item.href));
|
||||
return (
|
||||
<Link key={item.href} href={item.href}>
|
||||
<div className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${isActive ? "bg-blue-50 text-blue-600 border-r-2 border-blue-600" : "text-gray-600 hover:bg-gray-50"}`}>
|
||||
<Icon className="w-5 h-5 flex-shrink-0" />
|
||||
{!sidebarCollapsed && <span className="font-medium">{item.label}</span>}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{/* Rodapé com Avatar e Logout */}
|
||||
<div className="border-t p-4 mt-auto">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
{/* 4. A LÓGICA DO AVATAR AGORA É APLICADA AQUI */}
|
||||
<Avatar>
|
||||
<AvatarImage src={user.avatarFullUrl} />
|
||||
<AvatarFallback>{user.name.split(" ").map((n) => n[0]).join("")}</AvatarFallback>
|
||||
</Avatar>
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-gray-900 truncate">{user.name}</p>
|
||||
<p className="text-xs text-gray-500 truncate">{user.roles.join(', ')}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Button variant="outline" size="sm" className={sidebarCollapsed ? "w-full bg-transparent flex justify-center items-center p-2" : "w-full bg-transparent"} onClick={() => setShowLogoutDialog(true)}>
|
||||
<LogOut className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"} />
|
||||
{!sidebarCollapsed && "Sair"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className={`flex-1 flex flex-col transition-all duration-300 w-full ${sidebarCollapsed ? "ml-16" : "ml-64"}`}>
|
||||
<header className="bg-white border-b border-gray-200 px-4 md:px-6 py-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-4 flex-1 max-w-md"></div>
|
||||
<div className="flex items-center gap-4 ml-auto">
|
||||
<Button variant="ghost" size="sm" className="relative">
|
||||
<Bell className="w-5 h-5" />
|
||||
<Badge className="absolute -top-1 -right-1 w-5 h-5 p-0 flex items-center justify-center bg-red-500 text-white text-xs">1</Badge>
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
<main className="flex-1 p-4 md:p-6">{children}</main>
|
||||
</div>
|
||||
|
||||
{/* Dialog de Logout */}
|
||||
<Dialog open={showLogoutDialog} onOpenChange={setShowLogoutDialog}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader><DialogTitle>Confirmar Saída</DialogTitle><DialogDescription>Deseja realmente sair do sistema?</DialogDescription></DialogHeader>
|
||||
<DialogFooter className="flex gap-2">
|
||||
<Button variant="outline" onClick={() => setShowLogoutDialog(false)}>Cancelar</Button>
|
||||
<Button variant="destructive" onClick={confirmLogout}><LogOut className="mr-2 h-4 w-4" />Sair</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,117 +0,0 @@
|
||||
// CÓDIGO COMPLETO PARA: components/finance-layout.tsx
|
||||
|
||||
"use client";
|
||||
|
||||
import type React from "react";
|
||||
import { useState } from "react";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { useAuthLayout } from "@/hooks/useAuthLayout";
|
||||
import { api } from "@/services/api.mjs";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Home, Calendar, User, LogOut, ChevronLeft, ChevronRight, Bell } from "lucide-react";
|
||||
import { Badge } from "./ui/badge";
|
||||
|
||||
export default function FinancierLayout({ children }: { children: React.ReactNode }) {
|
||||
const { user, isLoading } = useAuthLayout({ requiredRole: 'finance' });
|
||||
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const confirmLogout = async () => {
|
||||
await api.logout();
|
||||
setShowLogoutDialog(false);
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
const menuItems = [
|
||||
{ href: "#", icon: Home, label: "Dashboard" },
|
||||
{ href: "#", icon: Calendar, label: "Relatórios financeiros" },
|
||||
{ href: "#", icon: User, label: "Finanças Gerais" },
|
||||
{ href: "#", icon: Calendar, label: "Configurações" },
|
||||
];
|
||||
|
||||
if (isLoading || !user) {
|
||||
return <div className="flex h-screen w-full items-center justify-center">Carregando...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex">
|
||||
<div className={`bg-card border-r border-border transition-all duration-300 ${sidebarCollapsed ? "w-16" : "w-64"} fixed left-0 top-0 h-screen flex flex-col z-10`}>
|
||||
<div className="p-4 border-b border-border">
|
||||
<div className="flex items-center justify-between">
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center"><div className="w-4 h-4 bg-primary-foreground rounded-sm"></div></div>
|
||||
<span className="font-semibold text-foreground">MediConnect</span>
|
||||
</div>
|
||||
)}
|
||||
<Button variant="ghost" size="sm" onClick={() => setSidebarCollapsed(!sidebarCollapsed)} className="p-1">
|
||||
{sidebarCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<nav className="flex-1 p-2 overflow-y-auto">
|
||||
{menuItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive = pathname === item.href || (item.href !== "/" && pathname.startsWith(item.href));
|
||||
return (
|
||||
<Link key={item.href} href={item.href}>
|
||||
<div className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${isActive ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"}`}>
|
||||
<Icon className="w-5 h-5 flex-shrink-0" />
|
||||
{!sidebarCollapsed && <span className="font-medium">{item.label}</span>}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<div className="border-t p-4 mt-auto">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
<Avatar>
|
||||
<AvatarImage src={user.avatarFullUrl} />
|
||||
<AvatarFallback>{user.name.split(" ").map((n) => n[0]).join("")}</AvatarFallback>
|
||||
</Avatar>
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-foreground truncate">{user.name}</p>
|
||||
<p className="text-xs text-muted-foreground truncate">{user.roles.join(', ')}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Button variant="outline" size="sm" className={sidebarCollapsed ? "w-full bg-transparent flex justify-center items-center p-2" : "w-full bg-transparent"} onClick={() => setShowLogoutDialog(true)}>
|
||||
<LogOut className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"} />
|
||||
{!sidebarCollapsed && "Sair"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`flex-1 flex flex-col transition-all duration-300 ${sidebarCollapsed ? "ml-16" : "ml-64"}`}>
|
||||
<header className="bg-card border-b border-border px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4 flex-1 max-w-md"></div>
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="sm" className="relative">
|
||||
<Bell className="w-5 h-5" />
|
||||
<Badge className="absolute -top-1 -right-1 w-5 h-5 p-0 flex items-center justify-center bg-destructive text-destructive-foreground text-xs">1</Badge>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main className="flex-1 p-6">{children}</main>
|
||||
</div>
|
||||
<Dialog open={showLogoutDialog} onOpenChange={setShowLogoutDialog}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader><DialogTitle>Confirmar Saída</DialogTitle><DialogDescription>Deseja realmente sair do sistema?</DialogDescription></DialogHeader>
|
||||
<DialogFooter className="flex gap-2">
|
||||
<Button variant="outline" onClick={() => setShowLogoutDialog(false)}>Cancelar</Button>
|
||||
<Button variant="destructive" onClick={confirmLogout}><LogOut className="mr-2 h-4 w-4" />Sair</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,121 +0,0 @@
|
||||
// CÓDIGO COMPLETO PARA: components/hospital-layout.tsx
|
||||
|
||||
"use client";
|
||||
|
||||
import type React from "react";
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import { useAuthLayout } from "@/hooks/useAuthLayout";
|
||||
import { api } from "@/services/api.mjs";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Home, Calendar, Clock, FileText, User, LogOut, ChevronLeft, ChevronRight, Bell, Search } from "lucide-react";
|
||||
import { Badge } from "./ui/badge";
|
||||
import { Input } from "./ui/input";
|
||||
|
||||
export default function HospitalLayout({ children }: { children: React.ReactNode }) {
|
||||
const { user, isLoading } = useAuthLayout({ requiredRole: 'patiente' });
|
||||
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const confirmLogout = async () => {
|
||||
await api.logout();
|
||||
setShowLogoutDialog(false);
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
const menuItems = [
|
||||
{ href: "/patient/dashboard", icon: Home, label: "Dashboard" },
|
||||
{ href: "/patient/appointments", icon: Calendar, label: "Minhas Consultas" },
|
||||
{ href: "/patient/schedule", icon: Clock, label: "Agendar Consulta" },
|
||||
{ href: "/patient/reports", icon: FileText, label: "Meus Laudos" },
|
||||
{ href: "/patient/profile", icon: User, label: "Meus Dados" },
|
||||
];
|
||||
|
||||
if (isLoading || !user) {
|
||||
return <div className="flex h-screen w-full items-center justify-center">Carregando...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex">
|
||||
<div className={`bg-card border-r border-border transition-all duration-300 ${sidebarCollapsed ? "w-16" : "w-64"} h-screen flex flex-col`}>
|
||||
<div className="p-4 border-b border-border">
|
||||
<div className="flex items-center justify-between">
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center"><div className="w-4 h-4 bg-primary-foreground rounded-sm"></div></div>
|
||||
<span className="font-semibold text-foreground">MediConnect</span>
|
||||
</div>
|
||||
)}
|
||||
<Button variant="ghost" size="sm" onClick={() => setSidebarCollapsed(!sidebarCollapsed)} className="p-1">
|
||||
{sidebarCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<nav className="flex-1 p-2">
|
||||
{menuItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive = pathname === item.href || (item.href !== "/" && pathname.startsWith(item.href));
|
||||
return (
|
||||
<Link key={item.href} href={item.href}>
|
||||
<div className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${isActive ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"}`}>
|
||||
<Icon className="w-5 h-5 flex-shrink-0" />
|
||||
{!sidebarCollapsed && <span className="font-medium">{item.label}</span>}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<div className="border-t p-4">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
<Avatar>
|
||||
<AvatarImage src={user.avatarFullUrl} />
|
||||
<AvatarFallback>{user.name.split(" ").map((n) => n[0]).join("")}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-foreground truncate">{user.name}</p>
|
||||
<p className="text-xs text-muted-foreground truncate">{user.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" className="w-full bg-transparent" onClick={() => setShowLogoutDialog(true)}>
|
||||
<LogOut className="mr-2 h-4 w-4" /> Sair
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col">
|
||||
<header className="bg-card border-b border-border px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4 flex-1 max-w-md">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4" />
|
||||
<Input placeholder="Buscar patiente" className="pl-10 bg-background border-border" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="sm" className="relative">
|
||||
<Bell className="w-5 h-5" />
|
||||
<Badge className="absolute -top-1 -right-1 w-5 h-5 p-0 flex items-center justify-center bg-destructive text-destructive-foreground text-xs">1</Badge>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main className="flex-1 p-6">{children}</main>
|
||||
</div>
|
||||
<Dialog open={showLogoutDialog} onOpenChange={setShowLogoutDialog}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader><DialogTitle>Confirmar Saída</DialogTitle><DialogDescription>Deseja realmente sair do sistema?</DialogDescription></DialogHeader>
|
||||
<DialogFooter className="flex gap-2">
|
||||
<Button variant="outline" onClick={() => setShowLogoutDialog(false)}>Cancelar</Button>
|
||||
<Button variant="destructive" onClick={confirmLogout}><LogOut className="mr-2 h-4 w-4" />Sair</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,134 +0,0 @@
|
||||
// CÓDIGO REATORADO PARA: components/manager-layout.tsx
|
||||
|
||||
"use client";
|
||||
|
||||
import type React from "react";
|
||||
import { useState } from "react";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { useAuthLayout } from "@/hooks/useAuthLayout"; // 1. Importamos nosso novo hook
|
||||
import { api } from "@/services/api.mjs";
|
||||
|
||||
// Componentes da UI (Button, Avatar, etc.)
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Home, Calendar, User, LogOut, ChevronLeft, ChevronRight, Bell } from "lucide-react";
|
||||
import { Badge } from "./ui/badge";
|
||||
|
||||
export default function ManagerLayout({ children }: { children: React.ReactNode }) {
|
||||
// 2. Usamos o hook para buscar o usuário e controlar o acesso
|
||||
const { user, isLoading } = useAuthLayout({ requiredRole: 'gestor' });
|
||||
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const confirmLogout = async () => {
|
||||
await api.logout();
|
||||
setShowLogoutDialog(false);
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
const menuItems = [
|
||||
{ href: "/manager/dashboard", icon: Home, label: "Dashboard" },
|
||||
{ href: "#", icon: Calendar, label: "Relatórios gerenciais" },
|
||||
{ href: "/manager/usuario", icon: User, label: "Gestão de Usuários" },
|
||||
{ href: "/manager/home", icon: User, label: "Gestão de Médicos" },
|
||||
{ href: "/manager/patientes", icon: User, label: "Gestão de patientes" },
|
||||
{ href: "#", icon: Calendar, label: "Configurações" },
|
||||
];
|
||||
|
||||
// 3. Enquanto o hook está carregando, mostramos uma tela de loading
|
||||
if (isLoading || !user) {
|
||||
return <div className="flex h-screen w-full items-center justify-center">Carregando...</div>;
|
||||
}
|
||||
|
||||
// O resto do seu JSX continua igual, mas agora usando a variável 'user' do hook
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex">
|
||||
<div className={`bg-white border-r border-gray-200 transition-all duration-300 fixed top-0 h-screen flex flex-col z-30 ${sidebarCollapsed ? "w-16" : "w-64"}`}>
|
||||
{/* Header da Sidebar */}
|
||||
<div className="p-4 border-b border-gray-200 flex items-center justify-between">
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
|
||||
<div className="w-4 h-4 bg-white rounded-sm"></div>
|
||||
</div>
|
||||
<span className="font-semibold text-gray-900">MediConnect</span>
|
||||
</div>
|
||||
)}
|
||||
<Button variant="ghost" size="sm" onClick={() => setSidebarCollapsed(!sidebarCollapsed)} className="p-1">
|
||||
{sidebarCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Menu */}
|
||||
<nav className="flex-1 p-2 overflow-y-auto">
|
||||
{menuItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive = pathname === item.href;
|
||||
return (
|
||||
<Link key={item.label} href={item.href}>
|
||||
<div className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${isActive ? "bg-blue-50 text-blue-600 border-r-2 border-blue-600" : "text-gray-600 hover:bg-gray-50"}`}>
|
||||
<Icon className="w-5 h-5 flex-shrink-0" />
|
||||
{!sidebarCollapsed && <span className="font-medium">{item.label}</span>}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{/* Rodapé com Avatar e Logout */}
|
||||
<div className="border-t p-4 mt-auto">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
{/* 4. A LÓGICA DO AVATAR AGORA É APLICADA AQUI */}
|
||||
<Avatar>
|
||||
<AvatarImage src={user.avatarFullUrl} />
|
||||
<AvatarFallback>{user.name.split(" ").map((n) => n[0]).join("")}</AvatarFallback>
|
||||
</Avatar>
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-gray-900 truncate">{user.name}</p>
|
||||
<p className="text-xs text-gray-500 truncate">{user.roles.join(', ')}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Button variant="outline" size="sm" className={sidebarCollapsed ? "w-full bg-transparent flex justify-center items-center p-2" : "w-full bg-transparent"} onClick={() => setShowLogoutDialog(true)}>
|
||||
<LogOut className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"} />
|
||||
{!sidebarCollapsed && "Sair"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className={`flex-1 flex flex-col transition-all duration-300 w-full ${sidebarCollapsed ? "ml-16" : "ml-64"}`}>
|
||||
<header className="bg-white border-b border-gray-200 px-4 md:px-6 py-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-4 flex-1 max-w-md"></div>
|
||||
<div className="flex items-center gap-4 ml-auto">
|
||||
<Button variant="ghost" size="sm" className="relative">
|
||||
<Bell className="w-5 h-5" />
|
||||
<Badge className="absolute -top-1 -right-1 w-5 h-5 p-0 flex items-center justify-center bg-red-500 text-white text-xs">1</Badge>
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
<main className="flex-1 p-4 md:p-6">{children}</main>
|
||||
</div>
|
||||
|
||||
{/* Dialog de Logout */}
|
||||
<Dialog open={showLogoutDialog} onOpenChange={setShowLogoutDialog}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Confirmar Saída</DialogTitle>
|
||||
<DialogDescription>Deseja realmente sair do sistema?</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="flex gap-2">
|
||||
<Button variant="outline" onClick={() => setShowLogoutDialog(false)}>Cancelar</Button>
|
||||
<Button variant="destructive" onClick={confirmLogout}><LogOut className="mr-2 h-4 w-4" />Sair</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,118 +0,0 @@
|
||||
// CÓDIGO COMPLETO PARA: components/patient-layout.tsx
|
||||
|
||||
"use client";
|
||||
|
||||
import type React from "react";
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import { useAuthLayout } from "@/hooks/useAuthLayout";
|
||||
import { api } from "@/services/api.mjs";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Home, Calendar, Clock, FileText, User, LogOut, ChevronLeft, ChevronRight, Bell } from "lucide-react";
|
||||
import { Badge } from "./ui/badge";
|
||||
|
||||
export default function patientLayout({ children }: { children: React.ReactNode }) {
|
||||
const { user, isLoading } = useAuthLayout({ requiredRole: 'patiente' });
|
||||
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const confirmLogout = async () => {
|
||||
await api.logout();
|
||||
setShowLogoutDialog(false);
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
const menuItems = [
|
||||
{ href: "/patient/dashboard", icon: Home, label: "Dashboard" },
|
||||
{ href: "/patient/appointments", icon: Calendar, label: "Minhas Consultas" },
|
||||
{ href: "/patient/schedule", icon: Clock, label: "Agendar Consulta" },
|
||||
{ href: "/patient/reports", icon: FileText, label: "Meus Laudos" },
|
||||
{ href: "/patient/profile", icon: User, label: "Meus Dados" },
|
||||
];
|
||||
|
||||
if (isLoading || !user) {
|
||||
return <div className="flex h-screen w-full items-center justify-center">Carregando...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex">
|
||||
<div className={`bg-card border-r border-border transition-all duration-300 ${sidebarCollapsed ? "w-16" : "w-64"} fixed left-0 top-0 h-screen flex flex-col z-10`}>
|
||||
<div className="p-4 border-b border-border">
|
||||
<div className="flex items-center justify-between">
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center"><div className="w-4 h-4 bg-primary-foreground rounded-sm"></div></div>
|
||||
<span className="font-semibold text-foreground">MediConnect</span>
|
||||
</div>
|
||||
)}
|
||||
<Button variant="ghost" size="sm" onClick={() => setSidebarCollapsed(!sidebarCollapsed)} className="p-1">
|
||||
{sidebarCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<nav className="flex-1 p-2 overflow-y-auto">
|
||||
{menuItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive = pathname === item.href || (item.href !== "/" && pathname.startsWith(item.href));
|
||||
return (
|
||||
<Link key={item.href} href={item.href}>
|
||||
<div className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${isActive ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"}`}>
|
||||
<Icon className="w-5 h-5 flex-shrink-0" />
|
||||
{!sidebarCollapsed && <span className="font-medium">{item.label}</span>}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<div className="border-t p-4 mt-auto">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
<Avatar>
|
||||
<AvatarImage src={user.avatarFullUrl} />
|
||||
<AvatarFallback>{user.name.split(" ").map((n) => n[0]).join("")}</AvatarFallback>
|
||||
</Avatar>
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-foreground truncate">{user.name}</p>
|
||||
<p className="text-xs text-muted-foreground truncate">{user.email}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Button variant="outline" size="sm" className={sidebarCollapsed ? "w-full bg-transparent flex justify-center items-center p-2" : "w-full bg-transparent"} onClick={() => setShowLogoutDialog(true)}>
|
||||
<LogOut className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"} />
|
||||
{!sidebarCollapsed && "Sair"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`flex-1 flex flex-col transition-all duration-300 ${sidebarCollapsed ? "ml-16" : "ml-64"}`}>
|
||||
<header className="bg-card border-b border-border px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4 flex-1 max-w-md"></div>
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="sm" className="relative">
|
||||
<Bell className="w-5 h-5" />
|
||||
<Badge className="absolute -top-1 -right-1 w-5 h-5 p-0 flex items-center justify-center bg-destructive text-destructive-foreground text-xs">1</Badge>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main className="flex-1 p-6">{children}</main>
|
||||
</div>
|
||||
<Dialog open={showLogoutDialog} onOpenChange={setShowLogoutDialog}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader><DialogTitle>Confirmar Saída</DialogTitle><DialogDescription>Deseja realmente sair do sistema?</DialogDescription></DialogHeader>
|
||||
<DialogFooter className="flex gap-2">
|
||||
<Button variant="outline" onClick={() => setShowLogoutDialog(false)}>Cancelar</Button>
|
||||
<Button variant="destructive" onClick={confirmLogout}><LogOut className="mr-2 h-4 w-4" />Sair</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,117 +0,0 @@
|
||||
// CÓDIGO COMPLETO PARA: components/secretary-layout.tsx
|
||||
|
||||
"use client";
|
||||
|
||||
import type React from "react";
|
||||
import { useState } from "react";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { useAuthLayout } from "@/hooks/useAuthLayout";
|
||||
import { api } from "@/services/api.mjs";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Home, Calendar, Clock, User, LogOut, ChevronLeft, ChevronRight, Bell } from "lucide-react";
|
||||
import { Badge } from "./ui/badge";
|
||||
|
||||
export default function SecretaryLayout({ children }: { children: React.ReactNode }) {
|
||||
const { user, isLoading } = useAuthLayout({ requiredRole: 'secretaria' });
|
||||
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const confirmLogout = async () => {
|
||||
await api.logout();
|
||||
setShowLogoutDialog(false);
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
const menuItems = [
|
||||
{ href: "/secretary/dashboard", icon: Home, label: "Dashboard" },
|
||||
{ href: "/secretary/appointments", icon: Calendar, label: "Consultas" },
|
||||
{ href: "/secretary/schedule", icon: Clock, label: "Agendar Consulta" },
|
||||
{ href: "/secretary/pacientes", icon: User, label: "pacientes" },
|
||||
];
|
||||
|
||||
if (isLoading || !user) {
|
||||
return <div className="flex h-screen w-full items-center justify-center">Carregando...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex">
|
||||
<div className={`bg-card border-r border-border transition-all duration-300 ${sidebarCollapsed ? "w-16" : "w-64"} fixed left-0 top-0 h-screen flex flex-col z-10`}>
|
||||
<div className="p-4 border-b border-border">
|
||||
<div className="flex items-center justify-between">
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center"><div className="w-4 h-4 bg-primary-foreground rounded-sm"></div></div>
|
||||
<span className="font-semibold text-foreground">MediConnect</span>
|
||||
</div>
|
||||
)}
|
||||
<Button variant="ghost" size="sm" onClick={() => setSidebarCollapsed(!sidebarCollapsed)} className="p-1">
|
||||
{sidebarCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<nav className="flex-1 p-2 overflow-y-auto">
|
||||
{menuItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive = pathname === item.href || (item.href !== "/" && pathname.startsWith(item.href));
|
||||
return (
|
||||
<Link key={item.href} href={item.href}>
|
||||
<div className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${isActive ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"}`}>
|
||||
<Icon className="w-5 h-5 flex-shrink-0" />
|
||||
{!sidebarCollapsed && <span className="font-medium">{item.label}</span>}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<div className="border-t p-4 mt-auto">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
<Avatar>
|
||||
<AvatarImage src={user.avatarFullUrl} />
|
||||
<AvatarFallback>{user.name.split(" ").map((n) => n[0]).join("")}</AvatarFallback>
|
||||
</Avatar>
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-foreground truncate">{user.name}</p>
|
||||
<p className="text-xs text-muted-foreground truncate">{user.email}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Button variant="outline" size="sm" className={sidebarCollapsed ? "w-full bg-transparent flex justify-center items-center p-2" : "w-full bg-transparent"} onClick={() => setShowLogoutDialog(true)}>
|
||||
<LogOut className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"} />
|
||||
{!sidebarCollapsed && "Sair"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`flex-1 flex flex-col transition-all duration-300 ${sidebarCollapsed ? "ml-16" : "ml-64"}`}>
|
||||
<header className="bg-card border-b border-border px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4 flex-1 max-w-md"></div>
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="sm" className="relative">
|
||||
<Bell className="w-5 h-5" />
|
||||
<Badge className="absolute -top-1 -right-1 w-5 h-5 p-0 flex items-center justify-center bg-destructive text-destructive-foreground text-xs">1</Badge>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main className="flex-1 p-6">{children}</main>
|
||||
</div>
|
||||
<Dialog open={showLogoutDialog} onOpenChange={setShowLogoutDialog}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader><DialogTitle>Confirmar Saída</DialogTitle><DialogDescription>Deseja realmente sair do sistema?</DialogDescription></DialogHeader>
|
||||
<DialogFooter className="flex gap-2">
|
||||
<Button variant="outline" onClick={() => setShowLogoutDialog(false)}>Cancelar</Button>
|
||||
<Button variant="destructive" onClick={confirmLogout}><LogOut className="mr-2 h-4 w-4" />Sair</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
125
components/ui/userToolTip.tsx
Normal file
125
components/ui/userToolTip.tsx
Normal file
@ -0,0 +1,125 @@
|
||||
"use client";
|
||||
|
||||
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CalendarCheck2, CalendarClock, ClipboardPlus, Home, LogOut, SquareUser } from "lucide-react";
|
||||
import {
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
} from "@/components/ui/popover";
|
||||
import { usePathname } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
|
||||
interface UserData {
|
||||
user_metadata: {
|
||||
full_name: string;
|
||||
};
|
||||
app_metadata: {
|
||||
user_role: string;
|
||||
};
|
||||
email: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
userData: UserData;
|
||||
sidebarCollapsed: boolean;
|
||||
handleLogout: () => void;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export default function SidebarUserSection({
|
||||
userData,
|
||||
sidebarCollapsed,
|
||||
handleLogout,
|
||||
isActive,
|
||||
}: Props) {
|
||||
const pathname = usePathname();
|
||||
const menuItems: any[] = [
|
||||
{ href: "/patient/schedule", icon: CalendarClock, label: "Agendar Consulta" },
|
||||
{ href: "/patient/appointments", icon: CalendarCheck2, label: "Minhas Consultas" },
|
||||
{ href: "/patient/reports", icon: ClipboardPlus, label: "Meus Laudos" },
|
||||
{ href: "/patient/profile", icon: SquareUser, label: "Meus Dados" },
|
||||
]
|
||||
return (
|
||||
<div className="border-t p-4 mt-auto">
|
||||
{/* POPUP DE INFORMAÇÕES DO USUÁRIO */}
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<div
|
||||
className={`flex items-center space-x-3 mb-4 p-2 rounded-md transition-colors ${
|
||||
isActive
|
||||
? "cursor-pointer hover:bg-gray-100"
|
||||
: "cursor-default pointer-events-none"
|
||||
}`}>
|
||||
<Avatar>
|
||||
<AvatarImage src="/placeholder.svg?height=40&width=40" />
|
||||
<AvatarFallback>
|
||||
{userData.user_metadata.full_name
|
||||
.split(" ")
|
||||
.map((n) => n[0])
|
||||
.join("")}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-gray-900 truncate">
|
||||
{userData.user_metadata.full_name}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 truncate">
|
||||
{userData.app_metadata.user_role}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
|
||||
{/* Card flutuante */}
|
||||
<PopoverContent
|
||||
align="center"
|
||||
side="top"
|
||||
className="w-64 p-4 shadow-lg border bg-white"
|
||||
>
|
||||
<nav>
|
||||
{menuItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive = pathname === item.href;
|
||||
return (
|
||||
<Link key={item.label} href={item.href}>
|
||||
<div
|
||||
className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${
|
||||
isActive
|
||||
? "bg-blue-50 text-blue-600 border-r-2 border-blue-600"
|
||||
: "text-gray-600 hover:bg-gray-50"
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-5 h-5 flex-shrink-0" />
|
||||
{!sidebarCollapsed && (
|
||||
<span className="font-medium">{item.label}</span>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
{/* Botão de sair */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className={
|
||||
sidebarCollapsed
|
||||
? "w-full bg-transparent flex justify-center items-center p-2"
|
||||
: "w-full bg-transparent"
|
||||
}
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<LogOut className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"} />
|
||||
{sidebarCollapsed && "Sair"}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -21,6 +21,10 @@ export const usersService = {
|
||||
return await api.post(`/functions/v1/create-user-with-password`, data);
|
||||
},
|
||||
|
||||
async getMeSimple() {
|
||||
return await api.post(`/functions/v1/user-info`);
|
||||
},
|
||||
|
||||
async full_data(user_id) {
|
||||
if (!user_id) throw new Error("user_id é obrigatório");
|
||||
|
||||
@ -57,4 +61,40 @@ export const usersService = {
|
||||
permissions,
|
||||
};
|
||||
},
|
||||
};
|
||||
async resetPassword(email) {
|
||||
if (!email) throw new Error("Email é obrigatório para resetar a senha.");
|
||||
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/auth/v1/recover`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
||||
},
|
||||
body: JSON.stringify({ email }),
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
const data = await res.json().catch(() => ({}));
|
||||
|
||||
|
||||
if (!res.ok) {
|
||||
console.error("Erro no resetPassword:", res.status, data);
|
||||
throw new Error(`Erro ${res.status}: ${data.message || "Falha ao resetar senha."}`);
|
||||
}
|
||||
|
||||
|
||||
console.log("✅ Reset de senha:", data);
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.error("❌ Erro na chamada resetPassword:", err);
|
||||
throw new Error(err.message || "Erro inesperado na recuperação de senha.");
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user