remocao das barras de persquisas

This commit is contained in:
m1guelmcf 2025-11-04 08:53:45 -03:00
parent 6c5b0604c2
commit ce938a7f2c
5 changed files with 759 additions and 543 deletions

View File

@ -11,352 +11,459 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import {
import { Search, Bell, Calendar, Clock, User, LogOut, Menu, X, Home, FileText, ChevronLeft, ChevronRight } from "lucide-react"; Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
Search,
Bell,
Calendar,
Clock,
User,
LogOut,
Menu,
X,
Home,
FileText,
ChevronLeft,
ChevronRight,
} from "lucide-react";
interface DoctorData { interface DoctorData {
id: string; id: string;
name: string; name: string;
email: string; email: string;
phone: string; phone: string;
cpf: string; cpf: string;
crm: string; crm: string;
specialty: string; specialty: string;
department: string; department: string;
permissions: object; permissions: object;
} }
interface PatientLayoutProps { interface PatientLayoutProps {
children: React.ReactNode; children: React.ReactNode;
} }
export default function DoctorLayout({ children }: PatientLayoutProps) { export default function DoctorLayout({ children }: PatientLayoutProps) {
const [doctorData, setDoctorData] = useState<DoctorData | null>(null); const [doctorData, setDoctorData] = useState<DoctorData | null>(null);
const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [showLogoutDialog, setShowLogoutDialog] = useState(false); const [showLogoutDialog, setShowLogoutDialog] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [windowWidth, setWindowWidth] = useState(0); const [windowWidth, setWindowWidth] = useState(0);
const isMobile = windowWidth < 1024; const isMobile = windowWidth < 1024;
const router = useRouter(); const router = useRouter();
const pathname = usePathname(); const pathname = usePathname();
useEffect(() => { useEffect(() => {
const userInfoString = localStorage.getItem("user_info"); const userInfoString = localStorage.getItem("user_info");
// --- ALTERAÇÃO PRINCIPAL AQUI --- // --- ALTERAÇÃO PRINCIPAL AQUI ---
// Procurando o token no localStorage, onde ele foi realmente salvo. // Procurando o token no localStorage, onde ele foi realmente salvo.
const token = localStorage.getItem("token"); const token = localStorage.getItem("token");
if (userInfoString && token) { if (userInfoString && token) {
const userInfo = JSON.parse(userInfoString); const userInfo = JSON.parse(userInfoString);
setDoctorData({ setDoctorData({
id: userInfo.id || "", id: userInfo.id || "",
name: userInfo.user_metadata?.full_name || "Doutor(a)", name: userInfo.user_metadata?.full_name || "Doutor(a)",
email: userInfo.email || "", email: userInfo.email || "",
specialty: userInfo.user_metadata?.specialty || "Especialidade", specialty: userInfo.user_metadata?.specialty || "Especialidade",
phone: userInfo.phone || "", phone: userInfo.phone || "",
cpf: "", cpf: "",
crm: "", crm: "",
department: "", department: "",
permissions: {}, permissions: {},
}); });
} else { } else {
// Se não encontrar, aí sim redireciona. // Se não encontrar, aí sim redireciona.
router.push("/login"); router.push("/login");
}
}, [router]);
// O restante do seu código permanece exatamente o mesmo...
useEffect(() => {
const handleResize = () => setWindowWidth(window.innerWidth);
handleResize();
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
useEffect(() => {
if (isMobile) {
setSidebarCollapsed(true);
} else {
setSidebarCollapsed(false);
}
}, [isMobile]);
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 toggleMobileMenu = () => {
setIsMobileMenuOpen(!isMobileMenuOpen);
};
const menuItems = [
{
href: "/doctor/dashboard",
icon: Home,
label: "Dashboard",
// Botão para o dashboard do médico
},
{
href: "/doctor/consultas",
icon: Calendar,
label: "Consultas",
// Botão para página de consultas marcadas do médico atual
},
{
href: "/doctor/medicos/editorlaudo",
icon: Clock,
label: "Editor de Laudo",
// Botão para página do editor de laudo
},
{
href: "/doctor/medicos",
icon: User,
label: "Pacientes",
// Botão para a página de visualização de todos os pacientes
},
{
href: "/doctor/disponibilidade",
icon: Calendar,
label: "Disponibilidade",
// Botão para o dashboard do médico
},
];
if (!doctorData) {
return <div>Carregando...</div>;
} }
}, [router]);
return ( // O restante do seu código permanece exatamente o mesmo...
// O restante do seu código JSX permanece exatamente o mesmo useEffect(() => {
<div className="min-h-screen bg-background flex"> const handleResize = () => setWindowWidth(window.innerWidth);
<div className={`bg-card border-r border transition-all duration-300 ${sidebarCollapsed ? "w-16" : "w-64"} fixed left-0 top-0 h-screen flex flex-col z-50`}> handleResize();
<div className="p-4 border-b border"> window.addEventListener("resize", handleResize);
<div className="flex items-center justify-between"> return () => window.removeEventListener("resize", handleResize);
{!sidebarCollapsed && ( }, []);
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center"> useEffect(() => {
<div className="w-4 h-4 bg-white rounded-sm"></div> if (isMobile) {
</div> setSidebarCollapsed(true);
<span className="font-semibold text-gray-900">MediConnect</span> } else {
</div> setSidebarCollapsed(false);
)} }
<Button variant="ghost" size="sm" onClick={() => setSidebarCollapsed(!sidebarCollapsed)} className="p-1"> }, [isMobile]);
{sidebarCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
</Button> const handleLogout = () => {
</div> 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 toggleMobileMenu = () => {
setIsMobileMenuOpen(!isMobileMenuOpen);
};
const menuItems = [
{
href: "/doctor/dashboard",
icon: Home,
label: "Dashboard",
// Botão para o dashboard do médico
},
{
href: "/doctor/consultas",
icon: Calendar,
label: "Consultas",
// Botão para página de consultas marcadas do médico atual
},
{
href: "/doctor/medicos/editorlaudo",
icon: Clock,
label: "Editor de Laudo",
// Botão para página do editor de laudo
},
{
href: "/doctor/medicos",
icon: User,
label: "Pacientes",
// Botão para a página de visualização de todos os pacientes
},
{
href: "/doctor/disponibilidade",
icon: Calendar,
label: "Disponibilidade",
// Botão para o dashboard do médico
},
];
if (!doctorData) {
return <div>Carregando...</div>;
}
return (
// O restante do seu código JSX permanece exatamente o mesmo
<div className="min-h-screen bg-background flex">
<div
className={`bg-card border-r border transition-all duration-300 ${
sidebarCollapsed ? "w-16" : "w-64"
} fixed left-0 top-0 h-screen flex flex-col z-50`}
>
<div className="p-4 border-b border">
<div className="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> </div>
<nav className="flex-1 p-2 overflow-y-auto"> <span className="font-semibold text-gray-900">MediConnect</span>
{menuItems.map((item) => { </div>
const Icon = item.icon; )}
const isActive = pathname === item.href || (item.href !== "/" && pathname.startsWith(item.href)); <Button
variant="ghost"
return ( size="sm"
<Link key={item.href} href={item.href}> onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
<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"}`}> className="p-1"
<Icon className="w-5 h-5 flex-shrink-0" /> >
{!sidebarCollapsed && <span className="font-medium">{item.label}</span>} {sidebarCollapsed ? (
</div> <ChevronRight className="w-4 h-4" />
</Link> ) : (
); <ChevronLeft className="w-4 h-4" />
})} )}
</nav> </Button>
// ... (seu código anterior) </div>
{/* Sidebar para desktop */}
<div className={`bg-white border-r border-gray-200 transition-all duration-300 ${sidebarCollapsed ? "w-16" : "w-64"} fixed left-0 top-0 h-screen flex flex-col z-50`}>
<div className="p-4 border-b border-gray-200">
<div className="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>
</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-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>
<div className="border-t p-4 mt-auto">
<div className="flex items-center space-x-3 mb-4">
{!sidebarCollapsed && (
<>
<Avatar>
<AvatarImage src="/placeholder.svg?height=40&width=40" />
<AvatarFallback>
{doctorData.name
.split(" ")
.map((n) => n[0])
.join("")}
</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-900 truncate">{doctorData.name}</p>
<p className="text-xs text-gray-500 truncate">{doctorData.specialty}</p>
</div>
</>
)}
{sidebarCollapsed && (
<Avatar className="mx-auto">
<AvatarImage src="/placeholder.svg?height=40&width=40" />
<AvatarFallback>
{doctorData.name
.split(" ")
.map((n) => n[0])
.join("")}
</AvatarFallback>
</Avatar>
)}
</div>
<div className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors text-muted-foreground hover:bg-accent cursor-pointer ${sidebarCollapsed ? "justify-center" : ""}`} onClick={handleLogout}>
<LogOut className="w-5 h-5 flex-shrink-0" />
{!sidebarCollapsed && <span className="font-medium">Sair</span>}
</div>
</div>
</div>
</div>
{isMobileMenuOpen && <div className="fixed inset-0 bg-black bg-opacity-50 z-40 md:hidden" onClick={toggleMobileMenu}></div>}
<div className={`bg-white border-r border-gray-200 fixed left-0 top-0 h-screen flex flex-col z-50 transition-transform duration-300 md:hidden ${isMobileMenuOpen ? "translate-x-0 w-64" : "-translate-x-full w-64"}`}>
<div className="p-4 border-b border-gray-200 flex items-center justify-between">
<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">Hospital System</span>
</div>
<Button variant="ghost" size="sm" onClick={toggleMobileMenu} className="p-1">
<X 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 || (item.href !== "/" && pathname.startsWith(item.href));
return (
<Link key={item.href} href={item.href} onClick={toggleMobileMenu}>
<div className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${isActive ? "bg-accent text-accent-foreground border-r-2 border-primary" : "text-muted-foreground hover:bg-accent"}`}>
<Icon className="w-5 h-5 flex-shrink-0" />
<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="/placeholder.svg?height=40&width=40" />
<AvatarFallback>
{doctorData.name
.split(" ")
.map((n) => n[0])
.join("")}
</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-900 truncate">{doctorData.name}</p>
<p className="text-xs text-gray-500 truncate">{doctorData.specialty}</p>
</div>
</div>
<Button
variant="outline"
size="sm"
className="w-full bg-transparent"
onClick={() => {
handleLogout();
toggleMobileMenu();
}}
>
<LogOut className="mr-2 h-4 w-4" />
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 px-6 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4 flex-1">
<div className="relative flex-1 max-w-md">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input placeholder="Buscar paciente" className="pl-10 bg-background 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-red-500 text-white 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? 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> </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-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>
// ... (seu código anterior)
{/* Sidebar para desktop */}
<div
className={`bg-white border-r border-gray-200 transition-all duration-300 ${
sidebarCollapsed ? "w-16" : "w-64"
} fixed left-0 top-0 h-screen flex flex-col z-50`}
>
<div className="p-4 border-b border-gray-200">
<div className="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>
</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-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>
<div className="border-t p-4 mt-auto">
<div className="flex items-center space-x-3 mb-4">
{!sidebarCollapsed && (
<>
<Avatar>
<AvatarImage src="/placeholder.svg?height=40&width=40" />
<AvatarFallback>
{doctorData.name
.split(" ")
.map((n) => n[0])
.join("")}
</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-900 truncate">
{doctorData.name}
</p>
<p className="text-xs text-gray-500 truncate">
{doctorData.specialty}
</p>
</div>
</>
)}
{sidebarCollapsed && (
<Avatar className="mx-auto">
<AvatarImage src="/placeholder.svg?height=40&width=40" />
<AvatarFallback>
{doctorData.name
.split(" ")
.map((n) => n[0])
.join("")}
</AvatarFallback>
</Avatar>
)}
</div>
<div
className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors text-muted-foreground hover:bg-accent cursor-pointer ${
sidebarCollapsed ? "justify-center" : ""
}`}
onClick={handleLogout}
>
<LogOut className="w-5 h-5 flex-shrink-0" />
{!sidebarCollapsed && <span className="font-medium">Sair</span>}
</div>
</div>
</div>
</div>
{isMobileMenuOpen && (
<div
className="fixed inset-0 bg-black bg-opacity-50 z-40 md:hidden"
onClick={toggleMobileMenu}
></div>
)}
<div
className={`bg-white border-r border-gray-200 fixed left-0 top-0 h-screen flex flex-col z-50 transition-transform duration-300 md:hidden ${
isMobileMenuOpen ? "translate-x-0 w-64" : "-translate-x-full w-64"
}`}
>
<div className="p-4 border-b border-gray-200 flex items-center justify-between">
<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">Hospital System</span>
</div>
<Button
variant="ghost"
size="sm"
onClick={toggleMobileMenu}
className="p-1"
>
<X 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 ||
(item.href !== "/" && pathname.startsWith(item.href));
return (
<Link key={item.href} href={item.href} onClick={toggleMobileMenu}>
<div
className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${
isActive
? "bg-accent text-accent-foreground border-r-2 border-primary"
: "text-muted-foreground hover:bg-accent"
}`}
>
<Icon className="w-5 h-5 flex-shrink-0" />
<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="/placeholder.svg?height=40&width=40" />
<AvatarFallback>
{doctorData.name
.split(" ")
.map((n) => n[0])
.join("")}
</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-900 truncate">
{doctorData.name}
</p>
<p className="text-xs text-gray-500 truncate">
{doctorData.specialty}
</p>
</div>
</div>
<Button
variant="outline"
size="sm"
className="w-full bg-transparent"
onClick={() => {
handleLogout();
toggleMobileMenu();
}}
>
<LogOut className="mr-2 h-4 w-4" />
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 px-6 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4 flex-1"></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-red-500 text-white 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? 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>
);
} }

View File

@ -6,14 +6,34 @@ import type React from "react";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useRouter, usePathname } from "next/navigation"; import { useRouter, usePathname } from "next/navigation";
import Link from "next/link"; import Link from "next/link";
import { api } from '@/services/api.mjs'; import { api } from "@/services/api.mjs";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import {
import { Search, Bell, Calendar, Clock, User, LogOut, Menu, X, Home, FileText, ChevronLeft, ChevronRight } from "lucide-react"; Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
Search,
Bell,
Calendar,
Clock,
User,
LogOut,
Menu,
X,
Home,
FileText,
ChevronLeft,
ChevronRight,
} from "lucide-react";
interface FinancierData { interface FinancierData {
id: string; id: string;
@ -30,7 +50,9 @@ interface PatientLayoutProps {
} }
export default function FinancierLayout({ children }: PatientLayoutProps) { export default function FinancierLayout({ children }: PatientLayoutProps) {
const [financierData, setFinancierData] = useState<FinancierData | null>(null); const [financierData, setFinancierData] = useState<FinancierData | null>(
null
);
const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [showLogoutDialog, setShowLogoutDialog] = useState(false); const [showLogoutDialog, setShowLogoutDialog] = useState(false);
const router = useRouter(); const router = useRouter();
@ -43,12 +65,13 @@ export default function FinancierLayout({ children }: PatientLayoutProps) {
if (userInfoString && token) { if (userInfoString && token) {
const userInfo = JSON.parse(userInfoString); const userInfo = JSON.parse(userInfoString);
setFinancierData({ setFinancierData({
id: userInfo.id || "", id: userInfo.id || "",
name: userInfo.user_metadata?.full_name || "Financeiro", name: userInfo.user_metadata?.full_name || "Financeiro",
email: userInfo.email || "", email: userInfo.email || "",
department: userInfo.user_metadata?.department || "Departamento Financeiro", department:
userInfo.user_metadata?.department || "Departamento Financeiro",
phone: userInfo.phone || "", phone: userInfo.phone || "",
cpf: "", cpf: "",
permissions: {}, permissions: {},
@ -79,18 +102,18 @@ export default function FinancierLayout({ children }: PatientLayoutProps) {
// --- ALTERAÇÃO 2: A função de logout agora é MUITO mais simples --- // --- ALTERAÇÃO 2: A função de logout agora é MUITO mais simples ---
const confirmLogout = async () => { const confirmLogout = async () => {
try { try {
// Chama a função centralizada para fazer o logout no servidor // Chama a função centralizada para fazer o logout no servidor
await api.logout(); await api.logout();
} catch (error) { } catch (error) {
// O erro já é logado dentro da função api.logout, não precisamos fazer nada aqui // O erro já é logado dentro da função api.logout, não precisamos fazer nada aqui
} finally { } finally {
// A responsabilidade do componente é apenas limpar o estado local e redirecionar // A responsabilidade do componente é apenas limpar o estado local e redirecionar
localStorage.removeItem("user_info"); localStorage.removeItem("user_info");
localStorage.removeItem("token"); localStorage.removeItem("token");
Cookies.remove("access_token"); // Limpeza de segurança Cookies.remove("access_token"); // Limpeza de segurança
setShowLogoutDialog(false); setShowLogoutDialog(false);
router.push("/"); // Redireciona para a home router.push("/"); // Redireciona para a home
} }
}; };
@ -106,7 +129,11 @@ export default function FinancierLayout({ children }: PatientLayoutProps) {
]; ];
if (!financierData) { if (!financierData) {
return <div className="flex h-screen w-full items-center justify-center">Carregando...</div>; return (
<div className="flex h-screen w-full items-center justify-center">
Carregando...
</div>
);
} }
return ( return (
@ -202,9 +229,7 @@ export default function FinancierLayout({ children }: PatientLayoutProps) {
} }
onClick={handleLogout} onClick={handleLogout}
> >
<LogOut <LogOut className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"} />
className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"}
/>
{!sidebarCollapsed && "Sair"} {!sidebarCollapsed && "Sair"}
</Button> </Button>
</div> </div>
@ -217,15 +242,7 @@ export default function FinancierLayout({ children }: PatientLayoutProps) {
> >
<header className="bg-card border-b border-border px-6 py-4"> <header className="bg-card border-b border-border px-6 py-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-4 flex-1 max-w-md"> <div className="flex items-center gap-4 flex-1 max-w-md"></div>
<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 paciente"
className="pl-10 bg-background border-border"
/>
</div>
</div>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Button variant="ghost" size="sm" className="relative"> <Button variant="ghost" size="sm" className="relative">
@ -263,4 +280,4 @@ export default function FinancierLayout({ children }: PatientLayoutProps) {
</Dialog> </Dialog>
</div> </div>
); );
} }

View File

@ -6,14 +6,30 @@ import { useState, useEffect } from "react";
import { useRouter, usePathname } from "next/navigation"; import { useRouter, usePathname } from "next/navigation";
import Link from "next/link"; import Link from "next/link";
import Cookies from "js-cookie"; // Mantido apenas para a limpeza de segurança no logout import Cookies from "js-cookie"; // Mantido apenas para a limpeza de segurança no logout
import { api } from '@/services/api.mjs'; import { api } from "@/services/api.mjs";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import {
import { Search, Bell, Calendar, User, LogOut, ChevronLeft, ChevronRight, Home } from "lucide-react"; Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
Search,
Bell,
Calendar,
User,
LogOut,
ChevronLeft,
ChevronRight,
Home,
} from "lucide-react";
interface ManagerData { interface ManagerData {
id: string; id: string;
@ -43,7 +59,7 @@ export default function ManagerLayout({ children }: ManagerLayoutProps) {
if (userInfoString && token) { if (userInfoString && token) {
const userInfo = JSON.parse(userInfoString); const userInfo = JSON.parse(userInfoString);
setManagerData({ setManagerData({
id: userInfo.id || "", id: userInfo.id || "",
name: userInfo.user_metadata?.full_name || "Gestor(a)", name: userInfo.user_metadata?.full_name || "Gestor(a)",
@ -77,18 +93,18 @@ export default function ManagerLayout({ children }: ManagerLayoutProps) {
// --- ALTERAÇÃO 2: A função de logout agora é MUITO mais simples --- // --- ALTERAÇÃO 2: A função de logout agora é MUITO mais simples ---
const confirmLogout = async () => { const confirmLogout = async () => {
try { try {
// Chama a função centralizada para fazer o logout no servidor // Chama a função centralizada para fazer o logout no servidor
await api.logout(); await api.logout();
} catch (error) { } catch (error) {
// O erro já é logado dentro da função api.logout, não precisamos fazer nada aqui // O erro já é logado dentro da função api.logout, não precisamos fazer nada aqui
} finally { } finally {
// A responsabilidade do componente é apenas limpar o estado local e redirecionar // A responsabilidade do componente é apenas limpar o estado local e redirecionar
localStorage.removeItem("user_info"); localStorage.removeItem("user_info");
localStorage.removeItem("token"); localStorage.removeItem("token");
Cookies.remove("access_token"); // Limpeza de segurança Cookies.remove("access_token"); // Limpeza de segurança
setShowLogoutDialog(false); setShowLogoutDialog(false);
router.push("/"); // Redireciona para a home router.push("/"); // Redireciona para a home
} }
}; };
@ -103,13 +119,19 @@ export default function ManagerLayout({ children }: ManagerLayoutProps) {
]; ];
if (!managerData) { if (!managerData) {
return <div className="flex h-screen w-full items-center justify-center">Carregando...</div>; return (
<div className="flex h-screen w-full items-center justify-center">
Carregando...
</div>
);
} }
return ( return (
<div className="min-h-screen bg-gray-50 flex"> <div className="min-h-screen bg-gray-50 flex">
<div <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"}`} 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"> <div className="p-4 border-b border-gray-200 flex items-center justify-between">
{!sidebarCollapsed && ( {!sidebarCollapsed && (
@ -117,9 +139,7 @@ export default function ManagerLayout({ children }: ManagerLayoutProps) {
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center"> <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 className="w-4 h-4 bg-white rounded-sm"></div>
</div> </div>
<span className="font-semibold text-gray-900"> <span className="font-semibold text-gray-900">MediConnect</span>
MediConnect
</span>
</div> </div>
)} )}
<Button <Button
@ -128,7 +148,11 @@ export default function ManagerLayout({ children }: ManagerLayoutProps) {
onClick={() => setSidebarCollapsed(!sidebarCollapsed)} onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
className="p-1" className="p-1"
> >
{sidebarCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />} {sidebarCollapsed ? (
<ChevronRight className="w-4 h-4" />
) : (
<ChevronLeft className="w-4 h-4" />
)}
</Button> </Button>
</div> </div>
@ -139,10 +163,16 @@ export default function ManagerLayout({ children }: ManagerLayoutProps) {
return ( return (
<Link key={item.label} href={item.href}> <Link key={item.label} href={item.href}>
<div <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"}`} 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" /> <Icon className="w-5 h-5 flex-shrink-0" />
{!sidebarCollapsed && <span className="font-medium">{item.label}</span>} {!sidebarCollapsed && (
<span className="font-medium">{item.label}</span>
)}
</div> </div>
</Link> </Link>
); );
@ -153,19 +183,32 @@ export default function ManagerLayout({ children }: ManagerLayoutProps) {
<div className="flex items-center space-x-3 mb-4"> <div className="flex items-center space-x-3 mb-4">
<Avatar> <Avatar>
<AvatarImage src="/placeholder.svg?height=40&width=40" /> <AvatarImage src="/placeholder.svg?height=40&width=40" />
<AvatarFallback>{managerData.name.split(" ").map((n) => n[0]).join("")}</AvatarFallback> <AvatarFallback>
{managerData.name
.split(" ")
.map((n) => n[0])
.join("")}
</AvatarFallback>
</Avatar> </Avatar>
{!sidebarCollapsed && ( {!sidebarCollapsed && (
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-900 truncate">{managerData.name}</p> <p className="text-sm font-medium text-gray-900 truncate">
<p className="text-xs text-gray-500 truncate">{managerData.department}</p> {managerData.name}
</p>
<p className="text-xs text-gray-500 truncate">
{managerData.department}
</p>
</div> </div>
)} )}
</div> </div>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
className={sidebarCollapsed ? "w-full bg-transparent flex justify-center items-center p-2" : "w-full bg-transparent"} className={
sidebarCollapsed
? "w-full bg-transparent flex justify-center items-center p-2"
: "w-full bg-transparent"
}
onClick={handleLogout} onClick={handleLogout}
> >
<LogOut className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"} /> <LogOut className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"} />
@ -174,18 +217,19 @@ export default function ManagerLayout({ children }: ManagerLayoutProps) {
</div> </div>
</div> </div>
<div className={`flex-1 flex flex-col transition-all duration-300 w-full ${sidebarCollapsed ? "ml-16" : "ml-64"}`}> <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"> <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 className="flex items-center gap-4 flex-1 max-w-md"></div>
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input placeholder="Buscar paciente" className="pl-10 bg-gray-50 border-gray-200" />
</div>
</div>
<div className="flex items-center gap-4 ml-auto"> <div className="flex items-center gap-4 ml-auto">
<Button variant="ghost" size="sm" className="relative"> <Button variant="ghost" size="sm" className="relative">
<Bell className="w-5 h-5" /> <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> <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> </Button>
</div> </div>
</header> </header>
@ -196,14 +240,22 @@ export default function ManagerLayout({ children }: ManagerLayoutProps) {
<DialogContent className="sm:max-w-md"> <DialogContent className="sm:max-w-md">
<DialogHeader> <DialogHeader>
<DialogTitle>Confirmar Saída</DialogTitle> <DialogTitle>Confirmar Saída</DialogTitle>
<DialogDescription>Deseja realmente sair do sistema? Você precisará fazer login novamente para acessar sua conta.</DialogDescription> <DialogDescription>
Deseja realmente sair do sistema? Você precisará fazer login
novamente para acessar sua conta.
</DialogDescription>
</DialogHeader> </DialogHeader>
<DialogFooter className="flex gap-2"> <DialogFooter className="flex gap-2">
<Button variant="outline" onClick={cancelLogout}>Cancelar</Button> <Button variant="outline" onClick={cancelLogout}>
<Button variant="destructive" onClick={confirmLogout}><LogOut className="mr-2 h-4 w-4" />Sair</Button> Cancelar
</Button>
<Button variant="destructive" onClick={confirmLogout}>
<LogOut className="mr-2 h-4 w-4" />
Sair
</Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div> </div>
); );
} }

View File

@ -1,52 +1,70 @@
"use client" "use client";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import type React from "react" import type React from "react";
import { useState, useEffect } from "react" import { useState, useEffect } from "react";
import Link from "next/link" import Link from "next/link";
import { useRouter, usePathname } from "next/navigation" import { useRouter, usePathname } from "next/navigation";
import { api } from "@/services/api.mjs"; // Importando nosso cliente de API import { api } from "@/services/api.mjs"; // Importando nosso cliente de API
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Search, Bell, User, LogOut, FileText, Clock, Calendar, Home, ChevronLeft, ChevronRight } from "lucide-react" import {
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog" Search,
Bell,
User,
LogOut,
FileText,
Clock,
Calendar,
Home,
ChevronLeft,
ChevronRight,
} from "lucide-react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
interface PatientData { interface PatientData {
name: string name: string;
email: string email: string;
phone: string phone: string;
cpf: string cpf: string;
birthDate: string birthDate: string;
address: string address: string;
} }
interface PatientLayoutProps { interface PatientLayoutProps {
children: React.ReactNode children: React.ReactNode;
} }
// --- ALTERAÇÃO 1: Renomeando o componente para maior clareza --- // --- ALTERAÇÃO 1: Renomeando o componente para maior clareza ---
export default function PatientLayout({ children }: PatientLayoutProps) { export default function PatientLayout({ children }: PatientLayoutProps) {
const [patientData, setPatientData] = useState<PatientData | null>(null) const [patientData, setPatientData] = useState<PatientData | null>(null);
const [sidebarCollapsed, setSidebarCollapsed] = useState(false) const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [showLogoutDialog, setShowLogoutDialog] = useState(false) const [showLogoutDialog, setShowLogoutDialog] = useState(false);
const router = useRouter() const router = useRouter();
const pathname = usePathname() const pathname = usePathname();
useEffect(() => { useEffect(() => {
const handleResize = () => { const handleResize = () => {
if (window.innerWidth < 1024) { if (window.innerWidth < 1024) {
setSidebarCollapsed(true) setSidebarCollapsed(true);
} else { } else {
setSidebarCollapsed(false) setSidebarCollapsed(false);
} }
} };
handleResize() handleResize();
window.addEventListener("resize", handleResize) window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize) return () => window.removeEventListener("resize", handleResize);
}, []) }, []);
useEffect(() => { useEffect(() => {
const userInfoString = localStorage.getItem("user_info"); const userInfoString = localStorage.getItem("user_info");
@ -55,12 +73,12 @@ export default function PatientLayout({ children }: PatientLayoutProps) {
if (userInfoString && token) { if (userInfoString && token) {
const userInfo = JSON.parse(userInfoString); const userInfo = JSON.parse(userInfoString);
setPatientData({ setPatientData({
name: userInfo.user_metadata?.full_name || "Paciente", name: userInfo.user_metadata?.full_name || "Paciente",
email: userInfo.email || "", email: userInfo.email || "",
phone: userInfo.phone || "", phone: userInfo.phone || "",
cpf: "", cpf: "",
birthDate: "", birthDate: "",
address: "", address: "",
}); });
@ -69,39 +87,47 @@ export default function PatientLayout({ children }: PatientLayoutProps) {
router.push("/login"); router.push("/login");
} }
}, [router]); }, [router]);
const handleLogout = () => setShowLogoutDialog(true) const handleLogout = () => setShowLogoutDialog(true);
// --- ALTERAÇÃO 4: Função de logout completa e padronizada --- // --- ALTERAÇÃO 4: Função de logout completa e padronizada ---
const confirmLogout = async () => { const confirmLogout = async () => {
try { try {
// Chama a função centralizada para fazer o logout no servidor // Chama a função centralizada para fazer o logout no servidor
await api.logout(); await api.logout();
} catch (error) { } catch (error) {
console.error("Erro ao tentar fazer logout no servidor:", error); console.error("Erro ao tentar fazer logout no servidor:", error);
} finally { } finally {
// Limpeza completa e consistente do estado local // Limpeza completa e consistente do estado local
localStorage.removeItem("user_info"); localStorage.removeItem("user_info");
localStorage.removeItem("token"); localStorage.removeItem("token");
Cookies.remove("access_token"); // Limpeza de segurança Cookies.remove("access_token"); // Limpeza de segurança
setShowLogoutDialog(false); setShowLogoutDialog(false);
router.push("/"); // Redireciona para a página inicial router.push("/"); // Redireciona para a página inicial
} }
}; };
const cancelLogout = () => setShowLogoutDialog(false) const cancelLogout = () => setShowLogoutDialog(false);
const menuItems = [ const menuItems = [
{ href: "/patient/dashboard", icon: Home, label: "Dashboard" }, { href: "/patient/dashboard", icon: Home, label: "Dashboard" },
{ href: "/patient/appointments", icon: Calendar, label: "Minhas Consultas" }, {
href: "/patient/appointments",
icon: Calendar,
label: "Minhas Consultas",
},
{ href: "/patient/schedule", icon: Clock, label: "Agendar Consulta" }, { href: "/patient/schedule", icon: Clock, label: "Agendar Consulta" },
{ href: "/patient/reports", icon: FileText, label: "Meus Laudos" }, { href: "/patient/reports", icon: FileText, label: "Meus Laudos" },
{ href: "/patient/profile", icon: User, label: "Meus Dados" }, { href: "/patient/profile", icon: User, label: "Meus Dados" },
] ];
if (!patientData) { if (!patientData) {
return <div className="flex h-screen w-full items-center justify-center">Carregando...</div>; return (
<div className="flex h-screen w-full items-center justify-center">
Carregando...
</div>
);
} }
return ( return (
@ -120,7 +146,9 @@ export default function PatientLayout({ children }: PatientLayoutProps) {
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center"> <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 className="w-4 h-4 bg-primary-foreground rounded-sm"></div>
</div> </div>
<span className="font-semibold text-foreground">MediConnect</span> <span className="font-semibold text-foreground">
MediConnect
</span>
</div> </div>
)} )}
<Button <Button
@ -141,10 +169,10 @@ export default function PatientLayout({ children }: PatientLayoutProps) {
{/* Menu */} {/* Menu */}
<nav className="flex-1 p-2 overflow-y-auto"> <nav className="flex-1 p-2 overflow-y-auto">
{menuItems.map((item) => { {menuItems.map((item) => {
const Icon = item.icon const Icon = item.icon;
const isActive = const isActive =
pathname === item.href || pathname === item.href ||
(item.href !== "/" && pathname.startsWith(item.href)) (item.href !== "/" && pathname.startsWith(item.href));
return ( return (
<Link key={item.href} href={item.href}> <Link key={item.href} href={item.href}>
@ -161,7 +189,7 @@ export default function PatientLayout({ children }: PatientLayoutProps) {
)} )}
</div> </div>
</Link> </Link>
) );
})} })}
</nav> </nav>
@ -199,9 +227,7 @@ export default function PatientLayout({ children }: PatientLayoutProps) {
} }
onClick={handleLogout} onClick={handleLogout}
> >
<LogOut <LogOut className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"} />{" "}
className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"}
/>{" "}
{/* Remove margem quando colapsado */} {/* Remove margem quando colapsado */}
{!sidebarCollapsed && "Sair"}{" "} {!sidebarCollapsed && "Sair"}{" "}
{/* Mostra o texto apenas quando não está colapsado */} {/* Mostra o texto apenas quando não está colapsado */}
@ -218,15 +244,7 @@ export default function PatientLayout({ children }: PatientLayoutProps) {
{/* Header */} {/* Header */}
<header className="bg-card border-b border-border px-6 py-4"> <header className="bg-card border-b border-border px-6 py-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-4 flex-1 max-w-md"> <div className="flex items-center gap-4 flex-1 max-w-md"></div>
<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 paciente"
className="pl-10 bg-background border-border"
/>
</div>
</div>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Button variant="ghost" size="sm" className="relative"> <Button variant="ghost" size="sm" className="relative">
@ -265,5 +283,5 @@ export default function PatientLayout({ children }: PatientLayoutProps) {
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div> </div>
) );
} }

View File

@ -1,41 +1,60 @@
// Caminho: app/(secretary)/layout.tsx (ou o caminho do seu arquivo) // Caminho: app/(secretary)/layout.tsx (ou o caminho do seu arquivo)
"use client" "use client";
import type React from "react" import type React from "react";
import { useState, useEffect } from "react" import { useState, useEffect } from "react";
import { useRouter, usePathname } from "next/navigation" import { useRouter, usePathname } from "next/navigation";
import Link from "next/link" import Link from "next/link";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { api } from '@/services/api.mjs'; // Importando nosso cliente de API central import { api } from "@/services/api.mjs"; // Importando nosso cliente de API central
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog" import {
import { Search, Bell, Calendar, Clock, User, LogOut, Home, ChevronLeft, ChevronRight } from "lucide-react" Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
Search,
Bell,
Calendar,
Clock,
User,
LogOut,
Home,
ChevronLeft,
ChevronRight,
} from "lucide-react";
interface SecretaryData { interface SecretaryData {
id: string id: string;
name: string name: string;
email: string email: string;
phone: string phone: string;
cpf: string cpf: string;
employeeId: string employeeId: string;
department: string department: string;
permissions: object permissions: object;
} }
interface SecretaryLayoutProps { interface SecretaryLayoutProps {
children: React.ReactNode children: React.ReactNode;
} }
export default function SecretaryLayout({ children }: SecretaryLayoutProps) { export default function SecretaryLayout({ children }: SecretaryLayoutProps) {
const [secretaryData, setSecretaryData] = useState<SecretaryData | null>(null); const [secretaryData, setSecretaryData] = useState<SecretaryData | null>(
const [sidebarCollapsed, setSidebarCollapsed] = useState(false) null
const [showLogoutDialog, setShowLogoutDialog] = useState(false) );
const router = useRouter() const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const pathname = usePathname() const [showLogoutDialog, setShowLogoutDialog] = useState(false);
const router = useRouter();
const pathname = usePathname();
useEffect(() => { useEffect(() => {
const userInfoString = localStorage.getItem("user_info"); const userInfoString = localStorage.getItem("user_info");
@ -44,7 +63,7 @@ export default function SecretaryLayout({ children }: SecretaryLayoutProps) {
if (userInfoString && token) { if (userInfoString && token) {
const userInfo = JSON.parse(userInfoString); const userInfo = JSON.parse(userInfoString);
setSecretaryData({ setSecretaryData({
id: userInfo.id || "", id: userInfo.id || "",
name: userInfo.user_metadata?.full_name || "Secretária", name: userInfo.user_metadata?.full_name || "Secretária",
@ -64,50 +83,53 @@ export default function SecretaryLayout({ children }: SecretaryLayoutProps) {
useEffect(() => { useEffect(() => {
const handleResize = () => { const handleResize = () => {
if (window.innerWidth < 1024) { if (window.innerWidth < 1024) {
setSidebarCollapsed(true) setSidebarCollapsed(true);
} else { } else {
setSidebarCollapsed(false) setSidebarCollapsed(false);
} }
} };
handleResize() handleResize();
window.addEventListener("resize", handleResize) window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize) return () => window.removeEventListener("resize", handleResize);
}, []) }, []);
const handleLogout = () => setShowLogoutDialog(true) const handleLogout = () => setShowLogoutDialog(true);
// --- ALTERAÇÃO 3: Função de logout completa e padronizada --- // --- ALTERAÇÃO 3: Função de logout completa e padronizada ---
const confirmLogout = async () => { const confirmLogout = async () => {
try { try {
// Chama a função centralizada para fazer o logout no servidor // Chama a função centralizada para fazer o logout no servidor
await api.logout(); await api.logout();
} catch (error) { } catch (error) {
console.error("Erro ao tentar fazer logout no servidor:", error); console.error("Erro ao tentar fazer logout no servidor:", error);
} finally { } finally {
// Limpeza completa e consistente do estado local // Limpeza completa e consistente do estado local
localStorage.removeItem("user_info"); localStorage.removeItem("user_info");
localStorage.removeItem("token"); localStorage.removeItem("token");
Cookies.remove("access_token"); // Limpeza de segurança Cookies.remove("access_token"); // Limpeza de segurança
setShowLogoutDialog(false); setShowLogoutDialog(false);
router.push("/"); // Redireciona para a página inicial router.push("/"); // Redireciona para a página inicial
} }
}; };
const cancelLogout = () => setShowLogoutDialog(false) const cancelLogout = () => setShowLogoutDialog(false);
const menuItems = [ const menuItems = [
{ href: "/secretary/dashboard", icon: Home, label: "Dashboard" }, { href: "/secretary/dashboard", icon: Home, label: "Dashboard" },
{ href: "/secretary/appointments", icon: Calendar, label: "Consultas" }, { href: "/secretary/appointments", icon: Calendar, label: "Consultas" },
{ href: "/secretary/schedule", icon: Clock, label: "Agendar Consulta" }, { href: "/secretary/schedule", icon: Clock, label: "Agendar Consulta" },
{ href: "/secretary/pacientes", icon: User, label: "Pacientes" }, { href: "/secretary/pacientes", icon: User, label: "Pacientes" },
] ];
if (!secretaryData) { if (!secretaryData) {
return <div className="flex h-screen w-full items-center justify-center">Carregando...</div>; return (
<div className="flex h-screen w-full items-center justify-center">
Carregando...
</div>
);
} }
return ( return (
<div className="min-h-screen bg-background flex"> <div className="min-h-screen bg-background flex">
{/* Sidebar */} {/* Sidebar */}
@ -123,7 +145,9 @@ export default function SecretaryLayout({ children }: SecretaryLayoutProps) {
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center"> <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 className="w-4 h-4 bg-primary-foreground rounded-sm"></div>
</div> </div>
<span className="font-semibold text-foreground">MediConnect</span> <span className="font-semibold text-foreground">
MediConnect
</span>
</div> </div>
)} )}
<Button <Button
@ -143,23 +167,27 @@ export default function SecretaryLayout({ children }: SecretaryLayoutProps) {
<nav className="flex-1 p-2 overflow-y-auto"> <nav className="flex-1 p-2 overflow-y-auto">
{menuItems.map((item) => { {menuItems.map((item) => {
const Icon = item.icon const Icon = item.icon;
const isActive = const isActive =
pathname === item.href || (item.href !== "/" && pathname.startsWith(item.href)) pathname === item.href ||
(item.href !== "/" && pathname.startsWith(item.href));
return ( return (
<Link key={item.href} href={item.href}> <Link key={item.href} href={item.href}>
<div <div
className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${isActive className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${
? "bg-accent text-accent-foreground" isActive
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground" ? "bg-accent text-accent-foreground"
}`} : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
}`}
> >
<Icon className="w-5 h-5 flex-shrink-0" /> <Icon className="w-5 h-5 flex-shrink-0" />
{!sidebarCollapsed && <span className="font-medium">{item.label}</span>} {!sidebarCollapsed && (
<span className="font-medium">{item.label}</span>
)}
</div> </div>
</Link> </Link>
) );
})} })}
</nav> </nav>
@ -179,7 +207,9 @@ export default function SecretaryLayout({ children }: SecretaryLayoutProps) {
<p className="text-sm font-medium text-foreground truncate"> <p className="text-sm font-medium text-foreground truncate">
{secretaryData.name} {secretaryData.name}
</p> </p>
<p className="text-xs text-muted-foreground truncate">{secretaryData.email}</p> <p className="text-xs text-muted-foreground truncate">
{secretaryData.email}
</p>
</div> </div>
)} )}
</div> </div>
@ -193,9 +223,7 @@ export default function SecretaryLayout({ children }: SecretaryLayoutProps) {
} }
onClick={handleLogout} onClick={handleLogout}
> >
<LogOut <LogOut className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"} />
className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"}
/>
{!sidebarCollapsed && "Sair"} {!sidebarCollapsed && "Sair"}
</Button> </Button>
</div> </div>
@ -203,20 +231,13 @@ export default function SecretaryLayout({ children }: SecretaryLayoutProps) {
{/* Main Content */} {/* Main Content */}
<div <div
className={`flex-1 flex flex-col transition-all duration-300 ${sidebarCollapsed ? "ml-16" : "ml-64" 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"> <header className="bg-card border-b border-border px-6 py-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-4 flex-1 max-w-md"> <div className="flex items-center gap-4 flex-1 max-w-md"></div>
<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 paciente"
className="pl-10 bg-background border-border"
/>
</div>
</div>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Button variant="ghost" size="sm" className="relative"> <Button variant="ghost" size="sm" className="relative">
@ -238,7 +259,8 @@ export default function SecretaryLayout({ children }: SecretaryLayoutProps) {
<DialogHeader> <DialogHeader>
<DialogTitle>Confirmar Saída</DialogTitle> <DialogTitle>Confirmar Saída</DialogTitle>
<DialogDescription> <DialogDescription>
Deseja realmente sair do sistema? Você precisará fazer login novamente para acessar sua conta. Deseja realmente sair do sistema? Você precisará fazer login
novamente para acessar sua conta.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<DialogFooter className="flex gap-2"> <DialogFooter className="flex gap-2">
@ -253,5 +275,5 @@ export default function SecretaryLayout({ children }: SecretaryLayoutProps) {
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div> </div>
) );
} }