resolve issues with new features

This commit is contained in:
Matthias Hochmeister
2026-03-12 17:20:32 +01:00
parent 68586b01dc
commit 34ca007f9b
8 changed files with 232 additions and 49 deletions

View File

@@ -17,6 +17,7 @@ import { Link } from 'react-router-dom';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { configApi } from '../../services/config';
import { notificationsApi } from '../../services/notifications';
import { nextcloudApi } from '../../services/nextcloud';
import { safeOpenUrl } from '../../utils/safeOpenUrl';
import ChatRoomList from './ChatRoomList';
import ChatMessageView from './ChatMessageView';
@@ -36,12 +37,27 @@ const ChatPanelInner: React.FC = () => {
});
const nextcloudUrl = externalLinks?.nextcloud;
// Keep a ref to rooms so the effect can access the latest list without
// re-running every time room data refreshes.
const roomsRef = React.useRef(rooms);
roomsRef.current = rooms;
React.useEffect(() => {
if (chatPanelOpen) {
// Dismiss our internal notification-centre entries
notificationsApi.dismissByType('nextcloud_talk').then(() => {
queryClient.invalidateQueries({ queryKey: ['notifications'] });
queryClient.invalidateQueries({ queryKey: ['unreadNotificationCount'] });
}).catch(() => {});
// Also mark all unread rooms as read directly in Nextcloud so that
// Nextcloud's own notification badges clear as well.
roomsRef.current
.filter((r) => r.unreadMessages > 0)
.forEach((r) => {
nextcloudApi.markAsRead(r.token).catch(() => {});
});
queryClient.invalidateQueries({ queryKey: ['nextcloud', 'rooms'] });
}
}, [chatPanelOpen, queryClient]);

View File

@@ -27,14 +27,16 @@ import {
ExpandLess,
} 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 { useAuth } from '../../contexts/AuthContext';
import { vehiclesApi } from '../../services/vehicles';
export { DRAWER_WIDTH, DRAWER_WIDTH_COLLAPSED };
interface SubItem {
text: string;
tabIndex: number;
path: string;
}
interface NavigationItem {
@@ -45,17 +47,17 @@ interface NavigationItem {
}
const kalenderSubItems: SubItem[] = [
{ text: 'Veranstaltungen', tabIndex: 0 },
{ text: 'Fahrzeugbuchungen', tabIndex: 1 },
{ text: 'Veranstaltungen', path: '/kalender?tab=0' },
{ text: 'Fahrzeugbuchungen', path: '/kalender?tab=1' },
];
const adminSubItems: SubItem[] = [
{ text: 'Services', tabIndex: 0 },
{ text: 'System', tabIndex: 1 },
{ text: 'Benutzer', tabIndex: 2 },
{ text: 'Broadcast', tabIndex: 3 },
{ text: 'Banner', tabIndex: 4 },
{ text: 'Wartung', tabIndex: 5 },
{ text: 'Services', path: '/admin?tab=0' },
{ text: 'System', path: '/admin?tab=1' },
{ text: 'Benutzer', path: '/admin?tab=2' },
{ text: 'Broadcast', path: '/admin?tab=3' },
{ text: 'Banner', path: '/admin?tab=4' },
{ text: 'Wartung', path: '/admin?tab=5' },
];
const baseNavigationItems: NavigationItem[] = [
@@ -123,9 +125,34 @@ function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) {
const isAdmin = user?.groups?.includes('dashboard_admin') ?? false;
const navigationItems = useMemo(() => {
return isAdmin ? [...baseNavigationItems, adminItem, adminSettingsItem] : baseNavigationItems;
}, [isAdmin]);
// Fetch vehicle list for dynamic dropdown sub-items
const { data: vehicleList } = useQuery({
queryKey: ['vehicles', 'sidebar'],
queryFn: () => vehiclesApi.getAll(),
staleTime: 2 * 60 * 1000,
});
const vehicleSubItems: SubItem[] = useMemo(
() =>
(vehicleList ?? []).map((v) => ({
text: v.kurzname ?? v.bezeichnung,
path: `/fahrzeuge/${v.id}`,
})),
[vehicleList],
);
const navigationItems = useMemo((): NavigationItem[] => {
const fahrzeugeItem: NavigationItem = {
text: 'Fahrzeuge',
icon: <DirectionsCar />,
path: '/fahrzeuge',
subItems: vehicleSubItems.length > 0 ? vehicleSubItems : undefined,
};
const items = baseNavigationItems.map((item) =>
item.path === '/fahrzeuge' ? fahrzeugeItem : item,
);
return isAdmin ? [...items, adminItem, adminSettingsItem] : items;
}, [isAdmin, vehicleSubItems]);
// Expand state for items with sub-items — auto-expand when route matches
const [expandedItems, setExpandedItems] = useState<Record<string, boolean>>({});
@@ -169,7 +196,7 @@ function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) {
>
<ListItemButton
selected={isActive}
onClick={() => handleNavigation(hasSubItems ? `${item.path}?tab=0` : item.path)}
onClick={() => handleNavigation(hasSubItems ? item.subItems![0].path : item.path)}
aria-label={`Zu ${item.text} navigieren`}
sx={{
justifyContent: sidebarCollapsed ? 'center' : 'initial',
@@ -219,14 +246,11 @@ function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) {
<Collapse in={expanded} timeout="auto" unmountOnExit>
<List disablePadding>
{item.subItems!.map((sub) => {
const subPath = `${item.path}?tab=${sub.tabIndex}`;
const isSubActive =
location.pathname === item.path &&
location.search === `?tab=${sub.tabIndex}`;
const isSubActive = location.pathname + location.search === sub.path;
return (
<ListItemButton
key={sub.tabIndex}
onClick={() => handleNavigation(subPath)}
key={sub.path}
onClick={() => handleNavigation(sub.path)}
selected={isSubActive}
sx={{
pl: 4,