- New CalendarPage with daily view (date navigation, slot list) and plans tab - Route /calendar added in App.tsx - Sidebar entry added after Proposals - Daily view: shows time slots with type, status, priority, duration, event data - Distinguishes real vs virtual (plan) slots visually - Plans tab: shows schedule plan rules with schedule parameters
70 lines
2.2 KiB
TypeScript
70 lines
2.2 KiB
TypeScript
import { useState, useEffect } from 'react'
|
|
import { Link, useLocation, useNavigate } from 'react-router-dom'
|
|
import api from '@/services/api'
|
|
import type { User } from '@/types'
|
|
|
|
interface Props {
|
|
user: User | null
|
|
onLogout: () => void
|
|
}
|
|
|
|
export default function Sidebar({ user, onLogout }: Props) {
|
|
const { pathname } = useLocation()
|
|
const navigate = useNavigate()
|
|
const [unreadCount, setUnreadCount] = useState(0)
|
|
|
|
useEffect(() => {
|
|
if (!user) {
|
|
setUnreadCount(0)
|
|
return
|
|
}
|
|
api.get<{ count: number }>('/notifications/count')
|
|
.then(({ data }) => setUnreadCount(data.count))
|
|
.catch(() => {})
|
|
const timer = setInterval(() => {
|
|
api.get<{ count: number }>('/notifications/count')
|
|
.then(({ data }) => setUnreadCount(data.count))
|
|
.catch(() => {})
|
|
}, 30000)
|
|
return () => clearInterval(timer)
|
|
}, [user])
|
|
|
|
const links = user ? [
|
|
{ to: '/', icon: '📊', label: 'Dashboard' },
|
|
{ to: '/projects', icon: '📁', label: 'Projects' },
|
|
{ to: '/proposals', icon: '💡', label: 'Proposals' },
|
|
{ to: '/calendar', icon: '📅', label: 'Calendar' },
|
|
{ to: '/notifications', icon: '🔔', label: 'Notifications' + (unreadCount > 0 ? ' (' + unreadCount + ')' : '') },
|
|
{ to: '/monitor', icon: '📡', label: 'Monitor' },
|
|
...(user.is_admin ? [
|
|
{ to: '/users', icon: '👥', label: 'Users' },
|
|
{ to: '/roles', icon: '🔐', label: 'Roles' },
|
|
] : []),
|
|
] : [
|
|
{ to: '/monitor', icon: '📡', label: 'Monitor' },
|
|
]
|
|
|
|
return (
|
|
<nav className="sidebar">
|
|
<div className="sidebar-header">
|
|
<h1>⚓ HarborForge</h1>
|
|
</div>
|
|
<ul className="nav-links">
|
|
{links.map((l) => (
|
|
<li key={l.to} className={pathname === l.to || (l.to !== '/' && pathname.startsWith(l.to)) ? 'active' : ''}>
|
|
<Link to={l.to}>{l.icon} {l.label}</Link>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
<div className="sidebar-footer">
|
|
<span>👤 {user ? user.username : 'Guest'}</span>
|
|
{user ? (
|
|
<button onClick={onLogout}>Log out</button>
|
|
) : (
|
|
<button onClick={() => navigate('/login')}>Log in</button>
|
|
)}
|
|
</div>
|
|
</nav>
|
|
)
|
|
}
|