eafc1c33fc
- FastAPI backend with SQLite database - React + TailwindCSS frontend - JWT authentication - Excel upload/download for attendance and accident records - Shift roster management with S7a/S7b split - Lateness/OT calculation based on shift times - Roster info API endpoints - Export API with token query parameter support - Docker compose deployment
128 lines
4.8 KiB
React
128 lines
4.8 KiB
React
import { Outlet, NavLink, useNavigate } from 'react-router-dom'
|
|
import { useAuth } from '../context/AuthContext'
|
|
import { LogOut, Users, AlertTriangle, LayoutDashboard, Upload, Calendar } from 'lucide-react'
|
|
|
|
export default function Layout() {
|
|
const { user, logout } = useAuth()
|
|
const navigate = useNavigate()
|
|
|
|
const handleLogout = () => {
|
|
logout()
|
|
navigate('/login')
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-slate-50">
|
|
{/* Header */}
|
|
<header className="bg-white border-b border-slate-200 sticky top-0 z-50">
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div className="flex items-center justify-between h-16">
|
|
{/* Logo */}
|
|
<div className="flex items-center gap-8">
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-8 h-8 bg-primary-600 rounded-lg flex items-center justify-center">
|
|
<span className="text-white font-bold text-sm">A</span>
|
|
</div>
|
|
<span className="text-xl font-bold text-slate-900">AARS</span>
|
|
</div>
|
|
|
|
{/* Navigation */}
|
|
<nav className="hidden md:flex items-center gap-1">
|
|
<NavLink
|
|
to="/"
|
|
end
|
|
className={({ isActive }) =>
|
|
`flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors ${
|
|
isActive
|
|
? 'bg-primary-50 text-primary-700'
|
|
: 'text-slate-600 hover:bg-slate-100'
|
|
}`
|
|
}
|
|
>
|
|
<LayoutDashboard size={18} />
|
|
Dashboard
|
|
</NavLink>
|
|
<NavLink
|
|
to="/attendance"
|
|
className={({ isActive }) =>
|
|
`flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors ${
|
|
isActive
|
|
? 'bg-primary-50 text-primary-700'
|
|
: 'text-slate-600 hover:bg-slate-100'
|
|
}`
|
|
}
|
|
>
|
|
<Users size={18} />
|
|
出勤 Attendance
|
|
</NavLink>
|
|
<NavLink
|
|
to="/accident"
|
|
className={({ isActive }) =>
|
|
`flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors ${
|
|
isActive
|
|
? 'bg-primary-50 text-primary-700'
|
|
: 'text-slate-600 hover:bg-slate-100'
|
|
}`
|
|
}
|
|
>
|
|
<AlertTriangle size={18} />
|
|
意外 Accident
|
|
</NavLink>
|
|
<NavLink
|
|
to="/roster"
|
|
className={({ isActive }) =>
|
|
`flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors ${
|
|
isActive
|
|
? 'bg-primary-50 text-primary-700'
|
|
: 'text-slate-600 hover:bg-slate-100'
|
|
}`
|
|
}
|
|
>
|
|
<Calendar size={18} />
|
|
班次 Roster
|
|
</NavLink>
|
|
</nav>
|
|
</div>
|
|
|
|
{/* Right side */}
|
|
<div className="flex items-center gap-4">
|
|
<NavLink
|
|
to="/upload"
|
|
className={({ isActive }) =>
|
|
`flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium transition-colors ${
|
|
isActive
|
|
? 'bg-primary-50 text-primary-700'
|
|
: 'text-slate-600 hover:bg-slate-100'
|
|
}`
|
|
}
|
|
>
|
|
<Upload size={16} />
|
|
<span className="hidden sm:inline">上傳</span>
|
|
</NavLink>
|
|
|
|
<div className="flex items-center gap-3 pl-4 border-l border-slate-200">
|
|
<div className="text-right hidden sm:block">
|
|
<div className="text-sm font-medium text-slate-900">{user?.name}</div>
|
|
<div className="text-xs text-slate-500">{user?.role}</div>
|
|
</div>
|
|
<button
|
|
onClick={handleLogout}
|
|
className="p-2 text-slate-400 hover:text-slate-600 hover:bg-slate-100 rounded-md transition-colors"
|
|
title="Logout"
|
|
>
|
|
<LogOut size={18} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
{/* Main content */}
|
|
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
<Outlet />
|
|
</main>
|
|
</div>
|
|
)
|
|
}
|