forked from RiseUP/riseup-squad21
Merge pull request 'merge-responsividade' (#14) from StsDanilo/riseup-squad21:merge-responsividade into main
Reviewed-on: RiseUP/riseup-squad21#14
This commit is contained in:
commit
652dc2745a
@ -32,6 +32,9 @@ export default function DoctorLayout({ children }: PatientLayoutProps) {
|
||||
const [doctorData, setDoctorData] = useState<DoctorData | null>(null);
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); // Novo estado para menu mobile
|
||||
const [windowWidth, setWindowWidth] = useState(0);
|
||||
const isMobile = windowWidth < 1024; // breakpoint lg
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
@ -44,6 +47,21 @@ export default function DoctorLayout({ children }: PatientLayoutProps) {
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => setWindowWidth(window.innerWidth);
|
||||
handleResize(); // inicializa com a largura atual
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isMobile) {
|
||||
setSidebarCollapsed(true);
|
||||
} else {
|
||||
setSidebarCollapsed(false);
|
||||
}
|
||||
}, [isMobile]);
|
||||
|
||||
const handleLogout = () => {
|
||||
setShowLogoutDialog(true);
|
||||
};
|
||||
@ -58,6 +76,10 @@ export default function DoctorLayout({ children }: PatientLayoutProps) {
|
||||
setShowLogoutDialog(false);
|
||||
};
|
||||
|
||||
const toggleMobileMenu = () => {
|
||||
setIsMobileMenuOpen(!isMobileMenuOpen);
|
||||
};
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
href: "#",
|
||||
@ -91,8 +113,44 @@ export default function DoctorLayout({ children }: PatientLayoutProps) {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex">
|
||||
{/* Sidebar */}
|
||||
<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-10`}>
|
||||
{/* 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">Hospital System</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>
|
||||
|
||||
// ... (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 && (
|
||||
@ -125,6 +183,85 @@ export default function DoctorLayout({ children }: PatientLayoutProps) {
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<div className="border-t p-4 mt-auto">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
{/* Se a sidebar estiver recolhida, o avatar e o texto do usuário também devem ser condensados ou ocultados */}
|
||||
{!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"> {/* Centraliza o avatar quando recolhido */}
|
||||
<AvatarImage src="/placeholder.svg?height=40&width=40" />
|
||||
<AvatarFallback>
|
||||
{doctorData.name
|
||||
.split(" ")
|
||||
.map((n) => n[0])
|
||||
.join("")}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Novo botão de sair, usando a mesma estrutura dos itens de menu */}
|
||||
<div
|
||||
className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors text-gray-600 hover:bg-gray-50 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>
|
||||
|
||||
{/* Sidebar para mobile (apresentado como um menu overlay) */}
|
||||
{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}> {/* Fechar menu ao clicar */}
|
||||
<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" />
|
||||
<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>
|
||||
@ -141,20 +278,21 @@ export default function DoctorLayout({ children }: PatientLayoutProps) {
|
||||
<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}>
|
||||
<Button variant="outline" size="sm" className="w-full bg-transparent" onClick={() => { handleLogout(); toggleMobileMenu(); }}> {/* Fechar menu ao deslogar */}
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
Sair
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Main Content */}
|
||||
<div className={`flex-1 flex flex-col transition-all duration-300 ${sidebarCollapsed ? "ml-16" : "ml-64"}`}>
|
||||
<div className={`flex-1 flex flex-col transition-all duration-300 ${sidebarCollapsed ? "ml-16" : "ml-64"}`}>
|
||||
{/* Header */}
|
||||
<header className="bg-white border-b border-gray-200 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">
|
||||
<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-gray-50 border-gray-200" />
|
||||
</div>
|
||||
@ -193,4 +331,4 @@ export default function DoctorLayout({ children }: PatientLayoutProps) {
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type React from "react";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
@ -9,187 +8,279 @@ 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, Clock, User, LogOut, Menu, X, Home, FileText, ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import {
|
||||
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 {
|
||||
id: string,
|
||||
name: string,
|
||||
email: string,
|
||||
phone: string,
|
||||
cpf: string,
|
||||
department: string,
|
||||
permissions: object,
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
cpf: string;
|
||||
department: string;
|
||||
permissions: object;
|
||||
}
|
||||
|
||||
interface PatientLayoutProps {
|
||||
children: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function FinancierLayout({ children }: PatientLayoutProps) {
|
||||
const [financierData, setFinancierData] = useState<FinancierData | null>(null);
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const [financierData, setFinancierData] = useState<FinancierData | null>(
|
||||
null
|
||||
);
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
const data = localStorage.getItem("financierData");
|
||||
if (data) {
|
||||
setFinancierData(JSON.parse(data));
|
||||
} else {
|
||||
router.push("/finance/login");
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
const handleLogout = () => {
|
||||
setShowLogoutDialog(true);
|
||||
};
|
||||
|
||||
const confirmLogout = () => {
|
||||
localStorage.removeItem("financierData");
|
||||
setShowLogoutDialog(false);
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
const cancelLogout = () => {
|
||||
setShowLogoutDialog(false);
|
||||
};
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
href: "#",
|
||||
icon: Home,
|
||||
label: "Dashboard",
|
||||
// Botão para o dashboard do médico
|
||||
},
|
||||
{
|
||||
href: "#",
|
||||
icon: Calendar,
|
||||
label: "Relatórios financeiros",
|
||||
// Botão para o dashboard do médico
|
||||
},
|
||||
|
||||
{
|
||||
href: "#",
|
||||
icon: User,
|
||||
label: "Finanças Gerais",
|
||||
// Botão para página do editor de laudo
|
||||
},
|
||||
{
|
||||
href: "#",
|
||||
icon: Calendar,
|
||||
label: "Configurações",
|
||||
// Botão para página de consultas marcadas do médico atual
|
||||
},
|
||||
];
|
||||
|
||||
if (!financierData) {
|
||||
return <div>Carregando...</div>;
|
||||
useEffect(() => {
|
||||
const data = localStorage.getItem("financierData");
|
||||
if (data) {
|
||||
setFinancierData(JSON.parse(data));
|
||||
} else {
|
||||
router.push("/finance/login");
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex">
|
||||
{/* Sidebar */}
|
||||
<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-10`}>
|
||||
<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">Hospital System</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>
|
||||
// 🔥 Responsividade automática da sidebar
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
// Ajuste o breakpoint conforme necessário. 1024px (lg) ou 768px (md) são comuns.
|
||||
if (window.innerWidth < 1024) {
|
||||
setSidebarCollapsed(true);
|
||||
} else {
|
||||
setSidebarCollapsed(false);
|
||||
}
|
||||
};
|
||||
|
||||
handleResize(); // executa na primeira carga
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, []);
|
||||
|
||||
const handleLogout = () => {
|
||||
setShowLogoutDialog(true);
|
||||
};
|
||||
|
||||
const confirmLogout = () => {
|
||||
localStorage.removeItem("financierData");
|
||||
setShowLogoutDialog(false);
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
const cancelLogout = () => {
|
||||
setShowLogoutDialog(false);
|
||||
};
|
||||
|
||||
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 (!financierData) {
|
||||
return <div>Carregando...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex">
|
||||
{/* Sidebar */}
|
||||
<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-10`}
|
||||
>
|
||||
<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>
|
||||
|
||||
<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">
|
||||
<Avatar>
|
||||
<AvatarImage src="/placeholder.svg?height=40&width=40" />
|
||||
<AvatarFallback>
|
||||
{financierData.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">{financierData.name}</p>
|
||||
<p className="text-xs text-gray-500 truncate">{financierData.department}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" className="w-full bg-transparent" onClick={handleLogout}>
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
Sair
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className={`flex-1 flex flex-col transition-all duration-300 ${sidebarCollapsed ? "ml-16" : "ml-64"}`}>
|
||||
{/* Header */}
|
||||
<header className="bg-white border-b border-gray-200 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-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">
|
||||
<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>
|
||||
|
||||
{/* Page Content */}
|
||||
<main className="flex-1 p-6">{children}</main>
|
||||
</div>
|
||||
|
||||
{/* Logout confirmation dialog */}
|
||||
<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>
|
||||
<span className="font-semibold text-gray-900">
|
||||
Hospital System
|
||||
</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>
|
||||
|
||||
{/* Footer user info */}
|
||||
<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>
|
||||
{financierData.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">
|
||||
{financierData.name}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 truncate">
|
||||
{financierData.department}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Botão Sair - ajustado para responsividade */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className={
|
||||
sidebarCollapsed
|
||||
? "w-full bg-transparent flex justify-center items-center p-2" // Centraliza o ícone quando colapsado
|
||||
: "w-full bg-transparent"
|
||||
}
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<LogOut
|
||||
className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"}
|
||||
/>{" "}
|
||||
{/* Remove margem quando colapsado */}
|
||||
{!sidebarCollapsed && "Sair"}{" "}
|
||||
{/* Mostra o texto apenas quando não está colapsado */}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div
|
||||
className={`flex-1 flex flex-col transition-all duration-300 ${
|
||||
sidebarCollapsed ? "ml-16" : "ml-64"
|
||||
}`}
|
||||
>
|
||||
{/* Header */}
|
||||
<header className="bg-white border-b border-gray-200 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-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">
|
||||
<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>
|
||||
|
||||
{/* Page Content */}
|
||||
<main className="flex-1 p-6">{children}</main>
|
||||
</div>
|
||||
|
||||
{/* Logout confirmation dialog */}
|
||||
<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,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type React from "react";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
@ -9,193 +8,251 @@ 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, Clock, User, LogOut, Menu, X, Home, FileText, ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Search,
|
||||
Bell,
|
||||
Calendar,
|
||||
User,
|
||||
LogOut,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Home,
|
||||
} from "lucide-react";
|
||||
|
||||
interface ManagerData {
|
||||
id: string,
|
||||
name: string,
|
||||
email: string,
|
||||
phone: string,
|
||||
cpf: string,
|
||||
department: string,
|
||||
permissions: object,
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
cpf: string;
|
||||
department: string;
|
||||
permissions: object;
|
||||
}
|
||||
|
||||
interface PatientLayoutProps {
|
||||
children: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function ManagerLayout({ children }: PatientLayoutProps) {
|
||||
const [managerData, setManagerData] = useState<ManagerData | null>(null);
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const [managerData, setManagerData] = useState<ManagerData | null>(null);
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
const data = localStorage.getItem("managerData");
|
||||
if (data) {
|
||||
setManagerData(JSON.parse(data));
|
||||
} else {
|
||||
router.push("/manager/login");
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
const handleLogout = () => {
|
||||
setShowLogoutDialog(true);
|
||||
};
|
||||
|
||||
const confirmLogout = () => {
|
||||
localStorage.removeItem("managerData");
|
||||
setShowLogoutDialog(false);
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
const cancelLogout = () => {
|
||||
setShowLogoutDialog(false);
|
||||
};
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
href: "#",
|
||||
icon: Home,
|
||||
label: "Dashboard",
|
||||
// Botão para o dashboard do médico
|
||||
},
|
||||
{
|
||||
href: "#",
|
||||
icon: Calendar,
|
||||
label: "Relatórios gerenciais",
|
||||
// Botão para o dashboard do médico
|
||||
},
|
||||
|
||||
{
|
||||
href: "#",
|
||||
icon: User,
|
||||
label: "Gestão de Usuários",
|
||||
// Botão para página do editor de laudo
|
||||
},
|
||||
{
|
||||
href: "#",
|
||||
icon: User,
|
||||
label: "Gestão de Médicos",
|
||||
// Botão para a página de visualização de todos os pacientes
|
||||
},
|
||||
{
|
||||
href: "#",
|
||||
icon: Calendar,
|
||||
label: "Configurações",
|
||||
// Botão para página de consultas marcadas do médico atual
|
||||
},
|
||||
];
|
||||
|
||||
if (!managerData) {
|
||||
return <div>Carregando...</div>;
|
||||
useEffect(() => {
|
||||
const data = localStorage.getItem("managerData");
|
||||
if (data) {
|
||||
setManagerData(JSON.parse(data));
|
||||
} else {
|
||||
router.push("/manager/login");
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex">
|
||||
{/* Sidebar */}
|
||||
<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-10`}>
|
||||
<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">Hospital System</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>
|
||||
// 🔥 Responsividade automática da sidebar
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (window.innerWidth < 1024) {
|
||||
setSidebarCollapsed(true); // colapsa em telas pequenas (lg breakpoint ~ 1024px)
|
||||
} else {
|
||||
setSidebarCollapsed(false); // expande em desktop
|
||||
}
|
||||
};
|
||||
|
||||
<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));
|
||||
handleResize(); // roda na primeira carga
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
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>
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, []);
|
||||
|
||||
<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>
|
||||
{managerData.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">{managerData.name}</p>
|
||||
<p className="text-xs text-gray-500 truncate">{managerData.department}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" className="w-full bg-transparent" onClick={handleLogout}>
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
Sair
|
||||
</Button>
|
||||
</div>
|
||||
const handleLogout = () => setShowLogoutDialog(true);
|
||||
|
||||
const confirmLogout = () => {
|
||||
localStorage.removeItem("managerData");
|
||||
setShowLogoutDialog(false);
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
const cancelLogout = () => setShowLogoutDialog(false);
|
||||
|
||||
const menuItems = [
|
||||
{ href: "#", icon: Home, label: "Dashboard" },
|
||||
{ href: "#", icon: Calendar, label: "Relatórios gerenciais" },
|
||||
{ href: "#", icon: User, label: "Gestão de Usuários" },
|
||||
{ href: "#", icon: User, label: "Gestão de Médicos" },
|
||||
{ href: "#", icon: Calendar, label: "Configurações" },
|
||||
];
|
||||
|
||||
if (!managerData) {
|
||||
return <div>Carregando...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex">
|
||||
{/* Sidebar */}
|
||||
<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"}`}
|
||||
>
|
||||
{/* Logo + collapse button */}
|
||||
<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">
|
||||
Hospital System
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className={`flex-1 flex flex-col transition-all duration-300 ${sidebarCollapsed ? "ml-16" : "ml-64"}`}>
|
||||
{/* Header */}
|
||||
<header className="bg-white border-b border-gray-200 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-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">
|
||||
<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>
|
||||
|
||||
{/* Page Content */}
|
||||
<main className="flex-1 p-6">{children}</main>
|
||||
</div>
|
||||
|
||||
{/* Logout confirmation dialog */}
|
||||
<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>
|
||||
)}
|
||||
<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 Items */}
|
||||
<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>
|
||||
|
||||
{/* Perfil no rodapé */}
|
||||
<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>
|
||||
{managerData.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">
|
||||
{managerData.name}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 truncate">
|
||||
{managerData.department}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Botão Sair - ajustado para responsividade */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className={
|
||||
sidebarCollapsed
|
||||
? "w-full bg-transparent flex justify-center items-center p-2" // Centraliza o ícone quando colapsado
|
||||
: "w-full bg-transparent"
|
||||
}
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<LogOut
|
||||
className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"}
|
||||
/>{" "}
|
||||
{/* Remove margem quando colapsado */}
|
||||
{!sidebarCollapsed && "Sair"}{" "}
|
||||
{/* Mostra o texto apenas quando não está colapsado */}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Conteúdo principal */}
|
||||
<div
|
||||
className={`flex-1 flex flex-col transition-all duration-300 w-full
|
||||
${sidebarCollapsed ? "ml-16" : "ml-64"}`}
|
||||
>
|
||||
{/* Header */}
|
||||
<header className="bg-white border-b border-gray-200 px-4 md:px-6 py-4 flex items-center justify-between">
|
||||
{/* Search */}
|
||||
<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-gray-400 w-4 h-4" />
|
||||
<Input
|
||||
placeholder="Buscar paciente"
|
||||
className="pl-10 bg-gray-50 border-gray-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Notifications */}
|
||||
<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>
|
||||
|
||||
{/* Page Content */}
|
||||
<main className="flex-1 p-4 md:p-6">{children}</main>
|
||||
</div>
|
||||
|
||||
{/* Logout confirmation dialog */}
|
||||
<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,7 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import type React from "react"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import Link from "next/link"
|
||||
import { useRouter, usePathname } from "next/navigation"
|
||||
@ -12,15 +11,11 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||||
import {
|
||||
Search,
|
||||
Bell,
|
||||
Settings,
|
||||
Users,
|
||||
UserCheck,
|
||||
Calendar,
|
||||
Clock,
|
||||
User,
|
||||
LogOut,
|
||||
FileText,
|
||||
BarChart3,
|
||||
Clock,
|
||||
Calendar,
|
||||
Home,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
@ -55,6 +50,21 @@ export default function HospitalLayout({ children }: HospitalLayoutProps) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
|
||||
// 🔹 Ajuste automático no resize
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (window.innerWidth < 1024) {
|
||||
setSidebarCollapsed(true) // colapsa no mobile
|
||||
} else {
|
||||
setSidebarCollapsed(false) // expande no desktop
|
||||
}
|
||||
}
|
||||
|
||||
handleResize()
|
||||
window.addEventListener("resize", handleResize)
|
||||
return () => window.removeEventListener("resize", handleResize)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const data = localStorage.getItem("patientData")
|
||||
if (data) {
|
||||
@ -64,9 +74,7 @@ export default function HospitalLayout({ children }: HospitalLayoutProps) {
|
||||
}
|
||||
}, [router])
|
||||
|
||||
const handleLogout = () => {
|
||||
setShowLogoutDialog(true)
|
||||
}
|
||||
const handleLogout = () => setShowLogoutDialog(true)
|
||||
|
||||
const confirmLogout = () => {
|
||||
localStorage.removeItem("patientData")
|
||||
@ -74,36 +82,14 @@ export default function HospitalLayout({ children }: HospitalLayoutProps) {
|
||||
router.push("/")
|
||||
}
|
||||
|
||||
const cancelLogout = () => {
|
||||
setShowLogoutDialog(false)
|
||||
}
|
||||
const cancelLogout = () => setShowLogoutDialog(false)
|
||||
|
||||
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",
|
||||
},
|
||||
{ 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 (!patientData) {
|
||||
@ -113,7 +99,12 @@ export default function HospitalLayout({ children }: HospitalLayoutProps) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex">
|
||||
{/* Sidebar */}
|
||||
<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-10`}>
|
||||
<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-10`}
|
||||
>
|
||||
{/* Header da Sidebar */}
|
||||
<div className="p-4 border-b border-gray-200">
|
||||
<div className="flex items-center justify-between">
|
||||
{!sidebarCollapsed && (
|
||||
@ -121,35 +112,54 @@ export default function HospitalLayout({ children }: HospitalLayoutProps) {
|
||||
<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>
|
||||
<span className="font-semibold text-gray-900">
|
||||
Hospital System
|
||||
</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
|
||||
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>
|
||||
|
||||
{/* Menu */}
|
||||
<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))
|
||||
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"
|
||||
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>}
|
||||
{!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">
|
||||
<Avatar>
|
||||
@ -161,27 +171,54 @@ export default function HospitalLayout({ children }: HospitalLayoutProps) {
|
||||
.join("")}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-gray-900 truncate">{patientData.name}</p>
|
||||
<p className="text-xs text-gray-500 truncate">{patientData.email}</p>
|
||||
</div>
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-gray-900 truncate">
|
||||
{patientData.name}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 truncate">
|
||||
{patientData.email}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Button variant="outline" size="sm" className="w-full bg-transparent" onClick={handleLogout}>
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
Sair
|
||||
{/* Botão Sair - ajustado para responsividade */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className={
|
||||
sidebarCollapsed
|
||||
? "w-full bg-transparent flex justify-center items-center p-2" // Centraliza o ícone quando colapsado
|
||||
: "w-full bg-transparent"
|
||||
}
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<LogOut
|
||||
className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"}
|
||||
/>{" "}
|
||||
{/* Remove margem quando colapsado */}
|
||||
{!sidebarCollapsed && "Sair"}{" "}
|
||||
{/* Mostra o texto apenas quando não está colapsado */}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Main Content */}
|
||||
<div className={`flex-1 flex flex-col transition-all duration-300 ${sidebarCollapsed ? "ml-16" : "ml-64"}`}>
|
||||
<div
|
||||
className={`flex-1 flex flex-col transition-all duration-300 ${
|
||||
sidebarCollapsed ? "ml-16" : "ml-64"
|
||||
}`}
|
||||
>
|
||||
{/* Header */}
|
||||
<header className="bg-white border-b border-gray-200 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-gray-400 w-4 h-4" />
|
||||
<Input placeholder="Buscar paciente" className="pl-10 bg-gray-50 border-gray-200" />
|
||||
<Input
|
||||
placeholder="Buscar paciente"
|
||||
className="pl-10 bg-gray-50 border-gray-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -206,7 +243,8 @@ export default function HospitalLayout({ children }: HospitalLayoutProps) {
|
||||
<DialogHeader>
|
||||
<DialogTitle>Confirmar Saída</DialogTitle>
|
||||
<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>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="flex gap-2">
|
||||
@ -222,4 +260,4 @@ export default function HospitalLayout({ children }: HospitalLayoutProps) {
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
"use client"
|
||||
|
||||
import type React from "react"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { useRouter, usePathname } from "next/navigation"
|
||||
import Link from "next/link"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
@ -17,17 +17,28 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Search, Bell, Calendar, Clock, User, LogOut, Menu, X, Home, FileText, ChevronLeft, ChevronRight } from "lucide-react"
|
||||
|
||||
import {
|
||||
Search,
|
||||
Bell,
|
||||
Calendar,
|
||||
Clock,
|
||||
User,
|
||||
LogOut,
|
||||
Home,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
} from "lucide-react"
|
||||
|
||||
interface SecretaryData {
|
||||
id: string,
|
||||
name: string,
|
||||
email: string,
|
||||
phone: string,
|
||||
cpf: string,
|
||||
employeeId: string,
|
||||
department: string,
|
||||
permissions: object,
|
||||
id: string
|
||||
name: string
|
||||
email: string
|
||||
phone: string
|
||||
cpf: string
|
||||
employeeId: string
|
||||
department: string
|
||||
permissions: object
|
||||
}
|
||||
|
||||
interface PatientLayoutProps {
|
||||
@ -35,70 +46,58 @@ interface PatientLayoutProps {
|
||||
}
|
||||
|
||||
export default function SecretaryLayout({ children }: PatientLayoutProps) {
|
||||
const [secretaryData, setSecretaryData] = useState<SecretaryData | null>(null)
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
|
||||
const [showLogoutDialog, setShowLogoutDialog] = useState(false)
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
|
||||
// 🔹 Colapsar no mobile e expandir no desktop automaticamente
|
||||
useEffect(() => {
|
||||
const data = localStorage.getItem("secretaryData")
|
||||
if (data) {
|
||||
setSecretaryData(JSON.parse(data))
|
||||
} else {
|
||||
router.push("/patient/login")
|
||||
const handleResize = () => {
|
||||
if (window.innerWidth < 1024) {
|
||||
setSidebarCollapsed(true)
|
||||
} else {
|
||||
setSidebarCollapsed(false)
|
||||
}
|
||||
}
|
||||
}, [router])
|
||||
|
||||
const handleLogout = () => {
|
||||
setShowLogoutDialog(true)
|
||||
}
|
||||
handleResize()
|
||||
window.addEventListener("resize", handleResize)
|
||||
return () => window.removeEventListener("resize", handleResize)
|
||||
}, [])
|
||||
|
||||
const handleLogout = () => setShowLogoutDialog(true)
|
||||
const confirmLogout = () => {
|
||||
localStorage.removeItem("secretaryData")
|
||||
setShowLogoutDialog(false)
|
||||
router.push("/")
|
||||
}
|
||||
|
||||
const cancelLogout = () => {
|
||||
setShowLogoutDialog(false)
|
||||
}
|
||||
const cancelLogout = () => setShowLogoutDialog(false)
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
href: "##",
|
||||
icon: Home,
|
||||
label: "Dashboard",
|
||||
// Botão para o dashboard da secretária
|
||||
},
|
||||
{
|
||||
href: "###",
|
||||
icon: Calendar,
|
||||
label: "Consultas",
|
||||
// Botão para página de consultas marcadas
|
||||
},
|
||||
{
|
||||
href: "#",
|
||||
icon: Clock,
|
||||
label: "Agendar Consulta",
|
||||
// Botão para página de agendamento da consulta para o paciente
|
||||
},
|
||||
{
|
||||
href: "/secretary/pacientes",
|
||||
icon: User,
|
||||
label: "Pacientes",
|
||||
// Botão para a página de visualização de todos os pacientes
|
||||
},
|
||||
{ href: "##", icon: Home, label: "Dashboard" },
|
||||
{ href: "###", icon: Calendar, label: "Consultas" },
|
||||
{ href: "#", icon: Clock, label: "Agendar Consulta" },
|
||||
{ href: "/secretary/pacientes", icon: User, label: "Pacientes" },
|
||||
]
|
||||
|
||||
if (!secretaryData) {
|
||||
return <div>Carregando...</div>
|
||||
const secretaryData: SecretaryData = {
|
||||
id: "1",
|
||||
name: "Secretária Exemplo",
|
||||
email: "secretaria@hospital.com",
|
||||
phone: "999999999",
|
||||
cpf: "000.000.000-00",
|
||||
employeeId: "12345",
|
||||
department: "Atendimento",
|
||||
permissions: {},
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex">
|
||||
{/* Sidebar */}
|
||||
<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-10`}>
|
||||
<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-10`}
|
||||
>
|
||||
<div className="p-4 border-b border-gray-200">
|
||||
<div className="flex items-center justify-between">
|
||||
{!sidebarCollapsed && (
|
||||
@ -109,8 +108,17 @@ export default function SecretaryLayout({ children }: PatientLayoutProps) {
|
||||
<span className="font-semibold text-gray-900">Hospital System</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
|
||||
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>
|
||||
@ -118,13 +126,16 @@ export default function SecretaryLayout({ children }: PatientLayoutProps) {
|
||||
<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))
|
||||
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"
|
||||
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" />
|
||||
@ -146,31 +157,63 @@ export default function SecretaryLayout({ children }: PatientLayoutProps) {
|
||||
.join("")}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-gray-900 truncate">{secretaryData.name}</p>
|
||||
<p className="text-xs text-gray-500 truncate">{secretaryData.email}</p>
|
||||
</div>
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-gray-900 truncate">
|
||||
{secretaryData.name}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 truncate">{secretaryData.email}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Button variant="outline" size="sm" className="w-full bg-transparent" onClick={handleLogout}>
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
Sair
|
||||
{/* Botão Sair - ajustado para responsividade */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className={
|
||||
sidebarCollapsed
|
||||
? "w-full bg-transparent flex justify-center items-center p-2" // Centraliza o ícone quando colapsado
|
||||
: "w-full bg-transparent"
|
||||
}
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<LogOut
|
||||
className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"}
|
||||
/>{" "}
|
||||
{/* Remove margem quando colapsado */}
|
||||
{!sidebarCollapsed && "Sair"}{" "}
|
||||
{/* Mostra o texto apenas quando não está colapsado */}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Main Content */}
|
||||
<div className={`flex-1 flex flex-col transition-all duration-300 ${sidebarCollapsed ? "ml-16" : "ml-64"}`}>
|
||||
<div
|
||||
className={`flex-1 flex flex-col transition-all duration-300 ${
|
||||
sidebarCollapsed ? "ml-16" : "ml-64"
|
||||
}`}
|
||||
>
|
||||
{/* Header */}
|
||||
<header className="bg-white border-b border-gray-200 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-gray-400 w-4 h-4" />
|
||||
<Input placeholder="Buscar paciente" className="pl-10 bg-gray-50 border-gray-200" />
|
||||
<Input
|
||||
placeholder="Buscar paciente"
|
||||
className="pl-10 bg-gray-50 border-gray-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Este botão no header parece ter sido uma cópia do botão "Sair" da sidebar.
|
||||
Removi a lógica de sidebarCollapsed aqui, pois o header é independente.
|
||||
Se a intenção era ter um botão de logout no header, ele não deve ser afetado pela sidebar.
|
||||
Ajustei para ser um botão de sino de notificação, como nos exemplos anteriores,
|
||||
já que você tem o ícone Bell importado e uma badge para notificação.
|
||||
Se você quer um botão de LogOut aqui, por favor, me avise!
|
||||
*/}
|
||||
<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">
|
||||
@ -207,4 +250,4 @@ export default function SecretaryLayout({ children }: PatientLayoutProps) {
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user