Add full AARS system - Attendance & Accident Record System
- 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
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user