feat: add full-page chat route and sidebar menu ordering

This commit is contained in:
Matthias Hochmeister
2026-03-27 18:00:58 +01:00
parent 1a66a66aab
commit c1b4a92a12
7 changed files with 284 additions and 24 deletions

View File

@@ -13,7 +13,7 @@ import Tooltip from '@mui/material/Tooltip';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import { useLayout, CHAT_PANEL_MIN_WIDTH, CHAT_PANEL_MAX_WIDTH } from '../../contexts/LayoutContext';
import { ChatProvider, useChat } from '../../contexts/ChatContext';
import { useChat } from '../../contexts/ChatContext';
import { Link } from 'react-router-dom';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { configApi } from '../../services/config';
@@ -341,11 +341,7 @@ const ChatPanelInner: React.FC = () => {
};
const ChatPanel: React.FC = () => {
return (
<ChatProvider>
<ChatPanelInner />
</ChatProvider>
);
return <ChatPanelInner />;
};
export default ChatPanel;

View File

@@ -1,10 +1,12 @@
import { useState, ReactNode } from 'react';
import { Box, Toolbar } from '@mui/material';
import { useLocation } from 'react-router-dom';
import Header from '../shared/Header';
import Sidebar from '../shared/Sidebar';
import { useAuth } from '../../contexts/AuthContext';
import Loading from '../shared/Loading';
import { LayoutProvider, useLayout, DRAWER_WIDTH, DRAWER_WIDTH_COLLAPSED } from '../../contexts/LayoutContext';
import { ChatProvider } from '../../contexts/ChatContext';
import ChatPanel from '../chat/ChatPanel';
interface DashboardLayoutProps {
@@ -15,6 +17,8 @@ function DashboardLayoutInner({ children }: DashboardLayoutProps) {
const [mobileOpen, setMobileOpen] = useState(false);
const { isLoading } = useAuth();
const { sidebarCollapsed, chatPanelOpen, chatPanelWidth } = useLayout();
const location = useLocation();
const onChatPage = location.pathname === '/chat';
const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
@@ -25,7 +29,7 @@ function DashboardLayoutInner({ children }: DashboardLayoutProps) {
}
const sidebarWidth = sidebarCollapsed ? DRAWER_WIDTH_COLLAPSED : DRAWER_WIDTH;
const chatWidth = chatPanelOpen ? chatPanelWidth : 64;
const chatWidth = onChatPage ? 0 : (chatPanelOpen ? chatPanelWidth : 64);
return (
<Box sx={{ display: 'flex', height: '100vh', overflow: 'hidden' }}>
@@ -48,7 +52,7 @@ function DashboardLayoutInner({ children }: DashboardLayoutProps) {
{children}
</Box>
<ChatPanel />
{!onChatPage && <ChatPanel />}
</Box>
);
}
@@ -56,7 +60,9 @@ function DashboardLayoutInner({ children }: DashboardLayoutProps) {
function DashboardLayout({ children }: DashboardLayoutProps) {
return (
<LayoutProvider>
<DashboardLayoutInner>{children}</DashboardLayoutInner>
<ChatProvider>
<DashboardLayoutInner>{children}</DashboardLayoutInner>
</ChatProvider>
</LayoutProvider>
);
}

View File

@@ -28,12 +28,14 @@ import {
LocalShipping,
BugReport,
BookOnline,
Forum,
} from '@mui/icons-material';
import { useNavigate, useLocation } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';
import { useLayout, DRAWER_WIDTH, DRAWER_WIDTH_COLLAPSED } from '../../contexts/LayoutContext';
import { usePermissionContext } from '../../contexts/PermissionContext';
import { vehiclesApi } from '../../services/vehicles';
import { preferencesApi } from '../../services/settings';
export { DRAWER_WIDTH, DRAWER_WIDTH_COLLAPSED };
@@ -69,6 +71,11 @@ const baseNavigationItems: NavigationItem[] = [
icon: <DashboardIcon />,
path: '/dashboard',
},
{
text: 'Chat',
icon: <Forum />,
path: '/chat',
},
{
text: 'Kalender',
icon: <CalendarMonth />,
@@ -169,6 +176,14 @@ function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) {
staleTime: 2 * 60 * 1000,
});
const { data: preferences } = useQuery({
queryKey: ['user-preferences'],
queryFn: preferencesApi.get,
staleTime: 5 * 60 * 1000,
});
const menuOrder: string[] = (preferences?.menuOrder as string[] | undefined) ?? [];
const vehicleSubItems: SubItem[] = useMemo(
() =>
(vehicleList ?? []).map((v) => ({
@@ -220,8 +235,21 @@ function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) {
return item;
})
.filter((item) => !item.permission || hasPermission(item.permission));
// Apply custom menu order: items in menuOrder are sorted to their index; rest keep relative order
if (menuOrder.length > 0) {
items.sort((a, b) => {
const aIdx = menuOrder.indexOf(a.path);
const bIdx = menuOrder.indexOf(b.path);
if (aIdx !== -1 && bIdx !== -1) return aIdx - bIdx;
if (aIdx !== -1) return -1;
if (bIdx !== -1) return 1;
return 0;
});
}
return hasPermission('admin:view') ? [...items, adminItem, adminSettingsItem] : items;
}, [vehicleSubItems, hasPermission]);
}, [vehicleSubItems, hasPermission, menuOrder]);
// Expand state for items with sub-items — auto-expand when route matches
const [expandedItems, setExpandedItems] = useState<Record<string, boolean>>({});