[CLAUDE] FE: TopBar + NotificationBell + UserMenu — ERP shell foundation
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled

Kiến trúc Layout giờ tách thành [sidebar] [topbar + content], foundation
để scale thêm module trong tương lai (HR, Accounting, Inventory...).

TopBar: title placeholder + NotificationBell + UserMenu (initials avatar
+ role badges + logout). UserMenu thay cho bottom-of-sidebar (cleaner).

NotificationBell:
- fe-admin: cảnh báo SLA (HĐ quá/sắp quá hạn, 24h window)
- fe-user: hộp thư chờ xử lý (items trong /contracts/inbox)
- refetchInterval: 60s
- Placeholder cho SignalR/email notifications thật sẽ thay bằng
  /api/notifications endpoint ở Tier 3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-04-21 15:16:15 +07:00
parent 2e43799046
commit 2b6f91c2b2
6 changed files with 416 additions and 49 deletions

View File

@ -1,15 +1,8 @@
import { Link, NavLink, Outlet } from 'react-router-dom'
import { LogOut, Circle, Inbox, FileText, Plus, type LucideIcon } from 'lucide-react'
import * as Icons from 'lucide-react'
import { useAuth } from '@/contexts/AuthContext'
import { Inbox, FileText, Plus } from 'lucide-react'
import { TopBar } from '@/components/TopBar'
import { cn } from '@/lib/cn'
function getIcon(name: string | null): LucideIcon {
if (!name) return Circle
const cand = (Icons as unknown as Record<string, LucideIcon>)[name]
return cand ?? Circle
}
// Menu fixed cho fe-user (không show tree động vì user-flow đơn giản)
const USER_MENU = [
{ to: '/inbox', label: 'HĐ chờ xử lý', icon: Inbox },
@ -18,8 +11,6 @@ const USER_MENU = [
]
export function Layout() {
const { user, logout } = useAuth()
return (
<div className="flex h-screen">
<aside className="flex w-64 flex-col border-r border-slate-200 bg-white">
@ -30,7 +21,7 @@ export function Layout() {
</div>
<nav className="flex-1 space-y-1 p-3">
{USER_MENU.map(item => {
const Icon = item.icon ?? getIcon(null)
const Icon = item.icon
return (
<NavLink
key={item.to}
@ -48,26 +39,13 @@ export function Layout() {
)
})}
</nav>
<div className="border-t border-slate-200 p-3">
<div className="mb-2 px-3 text-xs text-slate-500">
<div className="truncate font-medium text-slate-700">{user?.fullName}</div>
<div className="truncate">{user?.email}</div>
{user && user.roles.length > 0 && (
<div className="mt-1 font-mono text-[10px]">{user.roles.join(', ')}</div>
)}
</div>
<button
onClick={logout}
className="flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm text-slate-600 transition hover:bg-slate-100"
>
<LogOut className="h-4 w-4" />
Đăng xuất
</button>
</div>
</aside>
<main className="flex-1 overflow-auto">
<Outlet />
</main>
<div className="flex flex-1 flex-col overflow-hidden">
<TopBar />
<main className="flex-1 overflow-auto">
<Outlet />
</main>
</div>
</div>
)
}