Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 124 additions & 87 deletions client/src/features/admin/components/AdminLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { Outlet, NavLink, useNavigate } from "react-router-dom";
import React, { useState, useEffect, useRef } from "react";
import { Outlet, NavLink, useNavigate, useLocation } from "react-router-dom";
import { useAuthStore } from "../../../store/authStore";
import { motion } from "framer-motion";

Expand All @@ -16,6 +16,18 @@ import {
export const AdminLayout: React.FC = () => {
const { user, logout } = useAuthStore();
const navigate = useNavigate();
const location = useLocation();

// State engine managing navigation sidebar width expansion configuration matching the reference dashboard
const [sidebarExpanded, setSidebarExpanded] = useState<boolean>(false);
const mainScrollContainerRef = useRef<HTMLDivElement>(null);

// Handle immediate viewport layout reset on route change
useEffect(() => {
if (mainScrollContainerRef.current) {
mainScrollContainerRef.current.scrollTop = 0;
}
}, [location.pathname]);

const handleSignOut = () => {
logout();
Expand All @@ -27,123 +39,148 @@ export const AdminLayout: React.FC = () => {
name: "Admin Dashboard",
path: "/admin/dashboard",
icon: LayoutDashboard,
color: "text-blue-600",
},
{
name: "Manage Users",
path: "/admin/users",
icon: Users,
color: "text-amber-700",
},
{
name: "Manage Librarians",
path: "/admin/librarians",
icon: ShieldAlert,
color: "text-rose-600",
},
];

return (
<div className="flex h-screen w-screen overflow-hidden bg-amber-50/40 font-sans text-slate-800 antialiased selection:bg-amber-200">
{/* Sidebar Navigation - Warm Archival Minimalist Layout (Matching 72 width) */}
<aside className="w-72 bg-card-bg border-r border-amber-100 flex flex-col justify-between relative z-20 shadow-xs shrink-0">
<div className="p-6">
<div className="flex items-center gap-3.5 py-3 border-b border-slate-100 mb-6">
{/* Elegant Institutional Identity Icon */}
<div className="w-10 h-10 bg-slate-900 text-amber-100 rounded-lg flex items-center justify-center shadow-xs shrink-0">
<Library size={20} className="stroke-[2.2]" />
</div>

<div className="flex flex-col">
<span className="font-bold text-base tracking-tight text-text-main leading-tight">
LMS
</span>
<span className="text-xs text-slate-400 font-bold uppercase tracking-wider mt-0.5">
Admin Portal
</span>
</div>
<div className="w-screen h-screen overflow-hidden flex flex-col bg-[#F7FAFC] font-sans text-[#2D3748] antialiased selection:bg-[#2B6CB0]/10 select-none">

{/* Institutional Top Application Header Bar - Always visible, persistent foundation layout */}
<header className="h-20 w-full flex items-center justify-between px-5 bg-[#4b6993] border-b border-white/10 shadow-sm text-white shrink-0 z-40">

{/* Core Navigation Brand Click Area */}
<div
onClick={() => setSidebarExpanded(!sidebarExpanded)}
className="flex items-center gap-4 cursor-pointer group select-none"
title={sidebarExpanded ? "Collapse Navigation Menu" : "Expand Navigation Menu"}
>
<div className="w-10 h-10 bg-white/10 rounded-lg flex items-center justify-center border border-white/10 group-hover:bg-white/15 transition-all duration-150">
<Library size={20} className="stroke-[2.2] text-[#ffffff]" />
</div>

{/* Navigation links optimized with comfortable typography scales & indicators */}
<nav className="space-y-1.5">
{navItems.map((item) => {
const IconComponent = item.icon;
return (
<NavLink
key={item.path}
to={item.path}
className={({ isActive }) =>
`flex items-center gap-4 px-4 py-3.5 rounded-lg text-sm font-semibold tracking-wide transition-all duration-150 ${
isActive
? "bg-slate-100 text-slate-950 font-bold shadow-xs border-l-4 border-slate-900 rounded-l-none pl-3"
: "text-slate-600 hover:bg-slate-50 hover:text-slate-950"
}`
}
>
<IconComponent
size={18}
className={`stroke-[2.2] shrink-0 ${item.color}`}
/>
<span>{item.name}</span>
</NavLink>
);
})}
</nav>
</div>

{/* Sidebar Footer - Spacious Action Deck matches exactly */}
<div className="p-5 border-t border-slate-100 bg-slate-50/60">
<button
onClick={handleSignOut}
className="flex w-full items-center justify-center gap-2.5 px-4 py-3 rounded-lg text-sm font-bold text-orange-700 bg-orange-50 hover:bg-orange-100 border border-orange-200/60 transition-all cursor-pointer shadow-2xs"
>
<LogOut size={16} className="stroke-[2.5]" />
<span>Logout</span>
</button>
<div className="flex flex-col">
<span className="font-bold text-base tracking-tight leading-tight text-white flex items-center gap-2">
LMS
</span>
<span className="text-xs font-semibold uppercase tracking-wider font-sans text-white/70">
Admin Portal
</span>
</div>
</div>
</aside>

{/* Main Structural Area */}
<div className="flex-1 flex flex-col overflow-hidden">
{/* Header Frame - Formatted with identical Height h-22 and Side Padding */}
<header className="h-22 bg-card-bg border-b border-amber-100 flex items-center justify-between px-10 shadow-2xs shrink-0">
<div>
<h1 className="text-lg font-bold text-text-main tracking-tight">
Admin Dashboard
</h1>
<p className="text-sm text-slate-400 font-medium mt-0.5">
Core directory controls and active network administration
{/* User Identity Matrix */}
<div className="flex items-center gap-5">
<div className="text-right hidden sm:block">
<p className="text-xs font-semibold tracking-wide uppercase text-white/80">
ROLE: <span className="text-white font-bold">{user?.role || "ADMIN"}</span>
</p>
</div>

<div className="flex items-center gap-5">
<div className="text-right hidden sm:block">
<p className="text-xs text-slate-400 font-medium tracking-wide uppercase mt-1">
ROLE:{" "}
<span className="text-rose-600 font-bold">
{user?.role || "ADMIN"}
</span>
</p>
</div>
<div className="w-11 h-11 border border-white/10 bg-white/10 text-white rounded-lg flex items-center justify-center shadow-2xs">
<User size={18} className="stroke-[2.2]" />
</div>
</div>
</header>

{/* User Profile Avatar Frame */}
<div className="w-11 h-11 bg-slate-50 border border-border-main/60 text-text-main rounded-lg flex items-center justify-center shadow-2xs">
<User size={18} className="stroke-[2.2]" />
</div>
{/* Main Structural Application Framework Split */}
<div className="flex flex-1 w-full h-[calc(100vh-80px)] overflow-hidden">

{/* Persistent Left Icon/Expanded Navigation Sidebar Tracking Shell */}
<motion.aside
animate={{ width: sidebarExpanded ? 288 : 80 }}
transition={{ type: "spring", damping: 28, stiffness: 240 }}
className="h-full bg-[#1A365D] border-r border-white/10 shadow-md flex flex-col justify-between p-4 text-white shrink-0 z-30"
>
<div className="flex flex-col h-full overflow-y-auto overflow-x-hidden no-scrollbar">
{/* Navigation Routing Links */}
<nav className="space-y-1.5 flex-1">
{navItems.map((item) => {
const IconComponent = item.icon;
return (
<NavLink
key={item.path}
to={item.path}
className={({ isActive }) =>
/* ALIGNMENT FIX: Added border-l-4 border-transparent to the base layout so text content doesn't shift 4px left/right on active change toggle */
`flex items-center rounded-lg text-sm font-medium tracking-wide transition-all duration-150 h-12 overflow-hidden border-l-4 ${
isActive
? "bg-white/10 text-white font-bold border-l-[#D69E2E] rounded-l-none shadow-2xs"
: "text-white/70 hover:bg-white/5 hover:text-white border-l-transparent"
}`
}
>
{/* Centered box icon wrapper to protect collapse alignments */}
<div className="w-11 h-full flex items-center justify-center shrink-0">
<IconComponent size={18} className="stroke-[2.2]" />
</div>
{sidebarExpanded && (
<motion.span
initial={{ opacity: 0, x: -4 }}
animate={{ opacity: 1, x: 0 }}
className="truncate pl-1 pr-4"
>
{item.name}
</motion.span>
)}
</NavLink>
);
})}
</nav>
</div>
</header>

{/* Content View Injection Portal using your clean transition specs */}
<main className="flex-1 overflow-y-auto p-10 bg-transparent">
{/* Action Area Sidebar Footer */}
<div className="pt-4 border-t border-white/10 bg-[#1A365D] shrink-0 overflow-hidden">
<button
onClick={handleSignOut}
/* ALIGNMENT FIX: Dynamic flex row centering adjustments based on collapse layout context to lock the icon perfectly dead-center */
className={`flex items-center rounded-lg text-xs font-bold text-white bg-[#4b6993] hover:bg-[#2B6CB0]/90 border border-transparent transition-all cursor-pointer shadow-sm uppercase tracking-wider h-11 w-full ${
sidebarExpanded ? "justify-start" : "justify-center"
}`}
title="Logout Account"
>
{/* ALIGNMENT FIX: Balanced inner icon structural frame sizing */}
<div className={`${sidebarExpanded ? "w-11" : "w-auto"} h-full flex items-center justify-center shrink-0`}>
<LogOut size={14} className="stroke-[2.5]" />
</div>
{sidebarExpanded && (
<motion.span
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="truncate pr-4 pl-1"
>
Logout
</motion.span>
)}
</button>
</div>
</motion.aside>

{/* Workspace Canvas Container Block */}
<main
ref={mainScrollContainerRef}
className="flex-1 overflow-y-auto bg-transparent relative"
>
<motion.div
initial={{ opacity: 0, y: 4 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.2, ease: "easeOut" }}
className="h-full w-full"
>
<Outlet />
</motion.div>
</main>

</div>
</div>
);
};
};
20 changes: 10 additions & 10 deletions client/src/features/admin/components/DeleteLibrarianProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,27 @@ export const DeleteLibrarianProfile: React.FC<DeleteLibrarianProfileProps> = ({
if (!isOpen) return null;

return (
<div className="fixed inset-0 bg-slate-900/40 backdrop-blur-xs flex items-center justify-center z-50 p-4 font-sans">
<div className="fixed inset-0 bg-slate-900/40 backdrop-blur-sm flex items-center justify-center z-50 p-4 font-sans select-none">
{/* Container: Matches the clean off-white base with soft linen-amber border */}
<div className="bg-card-bg rounded-2xl shadow-xl w-full max-w-sm p-6 border border-amber-100/80 animate-zoom-in">
<div className="bg-white rounded-2xl shadow-xl w-full max-w-sm p-6 border border-gray-200">
<div className="text-center">
{/* Header: Crisp text-base alignment with deep slate-ink tone */}
<h3 className="text-base font-bold text-text-main tracking-tight">
<h3 className="text-lg font-bold text-[#1A365D] tracking-tight">
Revoke Administrative Access
</h3>

{/* Main Paragraph: Structured at text-sm slate-600 for high editorial legibility */}
<p className="text-sm text-slate-600 mt-4 leading-relaxed">
<p className="text-sm text-[#2D3748] mt-4 leading-relaxed">
Are you completely confident about stripping{" "}
<strong className="text-text-main font-bold">
<strong className="text-[#1A365D] font-bold">
"{librarianName}"
</strong>{" "}
of all administrative privileges and access layers?
</p>

{/* Callout Block: Shipped with premium cream/rose warning surface tokens */}
<div className="text-xs text-rose-900 font-medium mt-5 bg-rose-50/60 border border-rose-100 p-4 rounded-xl text-left leading-relaxed">
<span className="font-bold uppercase tracking-wider block mb-1 text-rose-950 text-[11px]">
<div className="mt-5 rounded-xl border border-rose-200 bg-rose-50 p-4 text-left text-xs text-rose-700 font-medium leading-relaxed">
<span className="block mb-1 text-[11px] font-bold uppercase tracking-widest text-rose-800">
⚠️ Critical Reminder:
</span>
This action completely cleanses their system profile registry. They
Expand All @@ -48,13 +48,13 @@ export const DeleteLibrarianProfile: React.FC<DeleteLibrarianProfileProps> = ({
</div>

{/* Modal Action Footers - Crisp Touchpoints */}
<div className="mt-6 flex justify-end gap-3 pt-5 border-t border-slate-100 text-xs font-bold tracking-wide">
<div className="mt-6 flex justify-end gap-3 pt-5 border-t border-gray-100 text-xs font-bold tracking-wide">
{/* Cancel/Abort Button */}
<button
type="button"
onClick={onClose}
disabled={isPending}
className="px-4 py-3 bg-slate-50 border border-border-main text-text-main rounded-xl transition-all hover:bg-slate-100 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
className="px-4 py-2.5 bg-white border border-gray-200 text-[#718096] rounded-xl transition-all hover:bg-gray-50 hover:text-[#1A365D] cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
>
Abort
</button>
Expand All @@ -64,7 +64,7 @@ export const DeleteLibrarianProfile: React.FC<DeleteLibrarianProfileProps> = ({
type="button"
onClick={onConfirm}
disabled={isPending}
className="px-4 py-3 bg-slate-900 hover:bg-slate-800 text-amber-50 rounded-xl transition-all cursor-pointer shadow-sm disabled:bg-slate-700 disabled:cursor-not-allowed min-w-32.5 text-center"
className="px-5 py-2.5 bg-[#2B6CB0] hover:bg-[#1A365D] text-white rounded-full transition-all cursor-pointer shadow-sm disabled:opacity-50 disabled:cursor-not-allowed text-center"
>
{isPending ? "Revoking..." : "Confirm Deletion"}
</button>
Expand Down
Loading
Loading