Major Features: - ✅ Multi-tenant architecture (tenant isolation) - ✅ Employee CRUD with lifecycle management (onboarding/offboarding) - ✅ Department tree structure with email domain management - ✅ Company info management (single-record editing) - ✅ System functions CRUD (permission management) - ✅ Email account management (multi-account per employee) - ✅ Keycloak SSO integration (auth.lab.taipei) - ✅ Redis session storage (10.1.0.254:6379) - Solves Cookie 4KB limitation - Cross-system session sharing - Sliding expiration (8 hours) - Automatic token refresh Technical Stack: Backend: - FastAPI + SQLAlchemy - PostgreSQL 16 (10.1.0.20:5433) - Keycloak Admin API integration - Docker Mailserver integration (SSH) - Alembic migrations Frontend: - Next.js 14 (App Router) - NextAuth 4 with Keycloak Provider - Redis session storage (ioredis) - Tailwind CSS Infrastructure: - Redis 7 (10.1.0.254:6379) - Session + Cache - Keycloak 26.1.0 (auth.lab.taipei) - Docker Mailserver (10.1.0.254) Architecture Highlights: - Session管理由 Keycloak + Redis 統一控制 - 支援多系統 (HR/WebMail/Calendar/Drive/Office) 共享 session - Token 自動刷新,異質服務整合 - 未來可無縫遷移到雲端 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
98 lines
2.6 KiB
TypeScript
98 lines
2.6 KiB
TypeScript
/**
|
|
* 麵包屑導航組件
|
|
*/
|
|
'use client'
|
|
|
|
import Link from 'next/link'
|
|
import { usePathname } from 'next/navigation'
|
|
|
|
interface BreadcrumbItem {
|
|
name: string
|
|
href?: string
|
|
}
|
|
|
|
export function Breadcrumb() {
|
|
const pathname = usePathname()
|
|
|
|
// 定義路徑對應的顯示名稱
|
|
const pathMap: Record<string, string> = {
|
|
dashboard: '系統首頁',
|
|
employees: '員工管理',
|
|
new: '新增員工',
|
|
edit: '編輯員工',
|
|
departments: '部門管理',
|
|
organization: '組織架構',
|
|
'system-functions': '系統功能管理',
|
|
'company-info': '公司資料維護',
|
|
}
|
|
|
|
// 解析路徑生成麵包屑
|
|
const generateBreadcrumbs = (): BreadcrumbItem[] => {
|
|
const paths = pathname.split('/').filter((path) => path)
|
|
const breadcrumbs: BreadcrumbItem[] = []
|
|
|
|
paths.forEach((path, index) => {
|
|
// 跳過數字 ID (例如 /employees/123)
|
|
if (/^\d+$/.test(path)) {
|
|
breadcrumbs.push({
|
|
name: `#${path}`,
|
|
href: paths.slice(0, index + 1).join('/'),
|
|
})
|
|
return
|
|
}
|
|
|
|
const name = pathMap[path] || path
|
|
const href = '/' + paths.slice(0, index + 1).join('/')
|
|
|
|
breadcrumbs.push({ name, href })
|
|
})
|
|
|
|
return breadcrumbs
|
|
}
|
|
|
|
const breadcrumbs = generateBreadcrumbs()
|
|
|
|
// 如果只有一層(例如只在 /dashboard),不顯示麵包屑
|
|
if (breadcrumbs.length <= 1) {
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<nav className="flex mb-6" aria-label="Breadcrumb">
|
|
<ol className="inline-flex items-center space-x-1 md:space-x-3">
|
|
{breadcrumbs.map((crumb, index) => {
|
|
const isLast = index === breadcrumbs.length - 1
|
|
|
|
return (
|
|
<li key={crumb.href || crumb.name} className="inline-flex items-center">
|
|
{index > 0 && (
|
|
<svg
|
|
className="w-4 h-4 text-gray-400 mx-1"
|
|
fill="currentColor"
|
|
viewBox="0 0 20 20"
|
|
>
|
|
<path
|
|
fillRule="evenodd"
|
|
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
|
clipRule="evenodd"
|
|
/>
|
|
</svg>
|
|
)}
|
|
{isLast ? (
|
|
<span className="text-sm font-medium text-gray-500">{crumb.name}</span>
|
|
) : (
|
|
<Link
|
|
href={crumb.href!}
|
|
className="text-sm font-medium text-gray-700 hover:text-blue-600 transition-colors"
|
|
>
|
|
{crumb.name}
|
|
</Link>
|
|
)}
|
|
</li>
|
|
)
|
|
})}
|
|
</ol>
|
|
</nav>
|
|
)
|
|
}
|