261 lines
6.9 KiB
TypeScript
261 lines
6.9 KiB
TypeScript
import { useState } from 'react';
|
|
import {
|
|
AppBar,
|
|
Toolbar,
|
|
Typography,
|
|
IconButton,
|
|
Menu,
|
|
MenuItem,
|
|
Avatar,
|
|
ListItemIcon,
|
|
Divider,
|
|
Box,
|
|
Tooltip,
|
|
} from '@mui/material';
|
|
import {
|
|
LocalFireDepartment,
|
|
Person,
|
|
Settings,
|
|
Logout,
|
|
Menu as MenuIcon,
|
|
Launch,
|
|
Chat,
|
|
} from '@mui/icons-material';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { useAuth } from '../../contexts/AuthContext';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import NotificationBell from './NotificationBell';
|
|
import { configApi } from '../../services/config';
|
|
import { useLayout } from '../../contexts/LayoutContext';
|
|
|
|
interface HeaderProps {
|
|
onMenuClick: () => void;
|
|
}
|
|
|
|
function Header({ onMenuClick }: HeaderProps) {
|
|
const { user, logout } = useAuth();
|
|
const navigate = useNavigate();
|
|
const { toggleChatPanel } = useLayout();
|
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
|
const [toolsAnchorEl, setToolsAnchorEl] = useState<null | HTMLElement>(null);
|
|
|
|
const { data: externalLinks } = useQuery({
|
|
queryKey: ['external-links'],
|
|
queryFn: () => configApi.getExternalLinks(),
|
|
staleTime: 10 * 60 * 1000,
|
|
enabled: !!user,
|
|
});
|
|
|
|
const handleMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
|
|
setAnchorEl(event.currentTarget);
|
|
};
|
|
|
|
const handleMenuClose = () => {
|
|
setAnchorEl(null);
|
|
};
|
|
|
|
const handleToolsOpen = (event: React.MouseEvent<HTMLElement>) => {
|
|
setToolsAnchorEl(event.currentTarget);
|
|
};
|
|
|
|
const handleToolsClose = () => {
|
|
setToolsAnchorEl(null);
|
|
};
|
|
|
|
const handleProfile = () => {
|
|
handleMenuClose();
|
|
navigate('/profile');
|
|
};
|
|
|
|
const handleSettings = () => {
|
|
handleMenuClose();
|
|
navigate('/settings');
|
|
};
|
|
|
|
const handleLogout = () => {
|
|
handleMenuClose();
|
|
logout();
|
|
};
|
|
|
|
const handleOpenExternal = (url: string) => {
|
|
handleToolsClose();
|
|
window.open(url, '_blank', 'noopener,noreferrer');
|
|
};
|
|
|
|
// Get initials for avatar
|
|
const getInitials = () => {
|
|
if (!user) return '?';
|
|
const initials = (user.given_name?.[0] || '') + (user.family_name?.[0] || '');
|
|
return initials || user.name?.[0] || '?';
|
|
};
|
|
|
|
const linkEntries = externalLinks
|
|
? Object.entries(externalLinks).filter(([, url]) => !!url)
|
|
: [];
|
|
|
|
const linkLabels: Record<string, string> = {
|
|
nextcloud: 'Nextcloud',
|
|
bookstack: 'BookStack',
|
|
vikunja: 'Vikunja',
|
|
};
|
|
|
|
return (
|
|
<AppBar
|
|
position="fixed"
|
|
sx={{
|
|
zIndex: (theme) => theme.zIndex.drawer + 1,
|
|
}}
|
|
>
|
|
<Toolbar>
|
|
<IconButton
|
|
color="inherit"
|
|
aria-label="Menü öffnen"
|
|
edge="start"
|
|
onClick={onMenuClick}
|
|
sx={{ mr: 2, display: { sm: 'none' } }}
|
|
>
|
|
<MenuIcon />
|
|
</IconButton>
|
|
|
|
<LocalFireDepartment sx={{ mr: 2 }} />
|
|
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
|
|
Feuerwehr Dashboard
|
|
</Typography>
|
|
|
|
{user && (
|
|
<>
|
|
{linkEntries.length > 0 && (
|
|
<>
|
|
<Tooltip title="Externe Tools">
|
|
<IconButton
|
|
color="inherit"
|
|
onClick={handleToolsOpen}
|
|
size="small"
|
|
aria-label="Externe Tools"
|
|
aria-controls="tools-menu"
|
|
aria-haspopup="true"
|
|
>
|
|
<Launch />
|
|
</IconButton>
|
|
</Tooltip>
|
|
|
|
<Menu
|
|
id="tools-menu"
|
|
anchorEl={toolsAnchorEl}
|
|
open={Boolean(toolsAnchorEl)}
|
|
onClose={handleToolsClose}
|
|
anchorOrigin={{
|
|
vertical: 'bottom',
|
|
horizontal: 'right',
|
|
}}
|
|
transformOrigin={{
|
|
vertical: 'top',
|
|
horizontal: 'right',
|
|
}}
|
|
PaperProps={{
|
|
elevation: 3,
|
|
sx: { minWidth: 180, mt: 1 },
|
|
}}
|
|
>
|
|
{linkEntries.map(([key, url]) => (
|
|
<MenuItem key={key} onClick={() => handleOpenExternal(url)}>
|
|
<ListItemIcon>
|
|
<Launch fontSize="small" />
|
|
</ListItemIcon>
|
|
{linkLabels[key] || key}
|
|
</MenuItem>
|
|
))}
|
|
</Menu>
|
|
</>
|
|
)}
|
|
|
|
<Tooltip title="Chat">
|
|
<IconButton
|
|
color="inherit"
|
|
onClick={toggleChatPanel}
|
|
size="small"
|
|
aria-label="Chat öffnen"
|
|
sx={{ ml: 0.5 }}
|
|
>
|
|
<Chat />
|
|
</IconButton>
|
|
</Tooltip>
|
|
|
|
<NotificationBell />
|
|
|
|
<IconButton
|
|
onClick={handleMenuOpen}
|
|
size="small"
|
|
aria-label="Benutzerkonto"
|
|
aria-controls="user-menu"
|
|
aria-haspopup="true"
|
|
sx={{ ml: 1 }}
|
|
>
|
|
<Avatar
|
|
sx={{
|
|
bgcolor: 'secondary.main',
|
|
width: 32,
|
|
height: 32,
|
|
fontSize: '0.875rem',
|
|
}}
|
|
>
|
|
{getInitials()}
|
|
</Avatar>
|
|
</IconButton>
|
|
|
|
<Menu
|
|
id="user-menu"
|
|
anchorEl={anchorEl}
|
|
open={Boolean(anchorEl)}
|
|
onClose={handleMenuClose}
|
|
anchorOrigin={{
|
|
vertical: 'bottom',
|
|
horizontal: 'right',
|
|
}}
|
|
transformOrigin={{
|
|
vertical: 'top',
|
|
horizontal: 'right',
|
|
}}
|
|
PaperProps={{
|
|
elevation: 3,
|
|
sx: { minWidth: 250, mt: 1 },
|
|
}}
|
|
>
|
|
<Box sx={{ px: 2, py: 1.5 }}>
|
|
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
|
|
{user.name}
|
|
</Typography>
|
|
<Typography variant="body2" color="text.secondary">
|
|
{user.email}
|
|
</Typography>
|
|
</Box>
|
|
<Divider />
|
|
<MenuItem onClick={handleProfile}>
|
|
<ListItemIcon>
|
|
<Person fontSize="small" />
|
|
</ListItemIcon>
|
|
Profil
|
|
</MenuItem>
|
|
<MenuItem onClick={handleSettings}>
|
|
<ListItemIcon>
|
|
<Settings fontSize="small" />
|
|
</ListItemIcon>
|
|
Einstellungen
|
|
</MenuItem>
|
|
<Divider />
|
|
<MenuItem onClick={handleLogout}>
|
|
<ListItemIcon>
|
|
<Logout fontSize="small" />
|
|
</ListItemIcon>
|
|
Abmelden
|
|
</MenuItem>
|
|
</Menu>
|
|
</>
|
|
)}
|
|
</Toolbar>
|
|
</AppBar>
|
|
);
|
|
}
|
|
|
|
export default Header;
|