248 lines
9.5 KiB
TypeScript
248 lines
9.5 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import {
|
|
Container,
|
|
Box,
|
|
Fade,
|
|
} from '@mui/material';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { useAuth } from '../contexts/AuthContext';
|
|
import { usePermissionContext } from '../contexts/PermissionContext';
|
|
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
|
import SkeletonCard from '../components/shared/SkeletonCard';
|
|
import UserProfile from '../components/dashboard/UserProfile';
|
|
import UpcomingEventsWidget from '../components/dashboard/UpcomingEventsWidget';
|
|
import AtemschutzDashboardCard from '../components/atemschutz/AtemschutzDashboardCard';
|
|
import EquipmentDashboardCard from '../components/equipment/EquipmentDashboardCard';
|
|
import VehicleDashboardCard from '../components/vehicles/VehicleDashboardCard';
|
|
import BookStackRecentWidget from '../components/dashboard/BookStackRecentWidget';
|
|
import BookStackSearchWidget from '../components/dashboard/BookStackSearchWidget';
|
|
import VikunjaMyTasksWidget from '../components/dashboard/VikunjaMyTasksWidget';
|
|
import VikunjaQuickAddWidget from '../components/dashboard/VikunjaQuickAddWidget';
|
|
import VikunjaOverdueNotifier from '../components/dashboard/VikunjaOverdueNotifier';
|
|
import AtemschutzExpiryNotifier from '../components/dashboard/AtemschutzExpiryNotifier';
|
|
import AdminStatusWidget from '../components/dashboard/AdminStatusWidget';
|
|
import AnnouncementBanner from '../components/dashboard/AnnouncementBanner';
|
|
import VehicleBookingQuickAddWidget from '../components/dashboard/VehicleBookingQuickAddWidget';
|
|
import VehicleBookingListWidget from '../components/dashboard/VehicleBookingListWidget';
|
|
import EventQuickAddWidget from '../components/dashboard/EventQuickAddWidget';
|
|
import LinksWidget from '../components/dashboard/LinksWidget';
|
|
import BannerWidget from '../components/dashboard/BannerWidget';
|
|
import WidgetGroup from '../components/dashboard/WidgetGroup';
|
|
import BestellungenWidget from '../components/dashboard/BestellungenWidget';
|
|
import ShopWidget from '../components/dashboard/ShopWidget';
|
|
import { preferencesApi } from '../services/settings';
|
|
import { configApi } from '../services/config';
|
|
import { WidgetKey } from '../constants/widgets';
|
|
|
|
function Dashboard() {
|
|
const { user } = useAuth();
|
|
const { hasPermission } = usePermissionContext();
|
|
const [dataLoading, setDataLoading] = useState(true);
|
|
|
|
const { data: preferences } = useQuery({
|
|
queryKey: ['user-preferences'],
|
|
queryFn: preferencesApi.get,
|
|
});
|
|
|
|
const { data: externalLinks } = useQuery({
|
|
queryKey: ['external-links'],
|
|
queryFn: () => configApi.getExternalLinks(),
|
|
staleTime: 10 * 60 * 1000,
|
|
});
|
|
|
|
const linkCollections = (externalLinks?.linkCollections ?? []).filter(
|
|
(c) => c.links.length > 0
|
|
);
|
|
|
|
const widgetVisible = (key: WidgetKey) => {
|
|
return preferences?.widgets?.[key] !== false;
|
|
};
|
|
|
|
useEffect(() => {
|
|
const timer = setTimeout(() => {
|
|
setDataLoading(false);
|
|
}, 800);
|
|
|
|
return () => clearTimeout(timer);
|
|
}, []);
|
|
|
|
return (
|
|
<DashboardLayout>
|
|
{/* Vikunja — Overdue Notifier (invisible, polling component — outside grid) */}
|
|
<VikunjaOverdueNotifier />
|
|
{/* Atemschutz — Expiry Notifier (invisible, polling component — outside grid) */}
|
|
<AtemschutzExpiryNotifier />
|
|
<Container maxWidth={false} disableGutters>
|
|
<Box
|
|
sx={{
|
|
display: 'grid',
|
|
gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))',
|
|
gap: 2.5,
|
|
alignItems: 'start',
|
|
}}
|
|
>
|
|
{/* Announcement Banner — spans full width, renders null when no banners */}
|
|
<AnnouncementBanner gridColumn="1 / -1" />
|
|
|
|
{/* User Profile Card — full width, contains welcome greeting */}
|
|
{user && (
|
|
<Box sx={{ gridColumn: '1 / -1' }}>
|
|
{dataLoading ? (
|
|
<SkeletonCard variant="detailed" />
|
|
) : (
|
|
<Fade in={true} timeout={600} style={{ transitionDelay: '100ms' }}>
|
|
<Box>
|
|
<UserProfile user={user} />
|
|
</Box>
|
|
</Fade>
|
|
)}
|
|
</Box>
|
|
)}
|
|
|
|
{/* Status Group */}
|
|
<WidgetGroup title="Status" gridColumn="1 / -1">
|
|
{hasPermission('fahrzeuge:widget') && widgetVisible('vehicles') && (
|
|
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '300ms' }}>
|
|
<Box>
|
|
<VehicleDashboardCard />
|
|
</Box>
|
|
</Fade>
|
|
)}
|
|
|
|
{hasPermission('ausruestung:widget') && widgetVisible('equipment') && (
|
|
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '350ms' }}>
|
|
<Box>
|
|
<EquipmentDashboardCard />
|
|
</Box>
|
|
</Fade>
|
|
)}
|
|
|
|
{hasPermission('atemschutz:widget') && widgetVisible('atemschutz') && (
|
|
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '400ms' }}>
|
|
<Box>
|
|
<AtemschutzDashboardCard />
|
|
</Box>
|
|
</Fade>
|
|
)}
|
|
|
|
{hasPermission('admin:view') && widgetVisible('adminStatus') && (
|
|
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '440ms' }}>
|
|
<Box>
|
|
<AdminStatusWidget />
|
|
</Box>
|
|
</Fade>
|
|
)}
|
|
|
|
{hasPermission('bestellungen:widget') && widgetVisible('bestellungen') && (
|
|
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '460ms' }}>
|
|
<Box>
|
|
<BestellungenWidget />
|
|
</Box>
|
|
</Fade>
|
|
)}
|
|
|
|
{hasPermission('shop:widget') && widgetVisible('shopRequests') && (
|
|
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '470ms' }}>
|
|
<Box>
|
|
<ShopWidget />
|
|
</Box>
|
|
</Fade>
|
|
)}
|
|
</WidgetGroup>
|
|
|
|
{/* Kalender Group */}
|
|
<WidgetGroup title="Kalender" gridColumn="1 / -1">
|
|
{hasPermission('kalender:view') && widgetVisible('events') && (
|
|
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '480ms' }}>
|
|
<Box>
|
|
<UpcomingEventsWidget />
|
|
</Box>
|
|
</Fade>
|
|
)}
|
|
|
|
{hasPermission('kalender:view_bookings') && widgetVisible('vehicleBookingList') && (
|
|
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '520ms' }}>
|
|
<Box>
|
|
<VehicleBookingListWidget />
|
|
</Box>
|
|
</Fade>
|
|
)}
|
|
|
|
{hasPermission('kalender:manage_bookings') && widgetVisible('vehicleBooking') && (
|
|
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '560ms' }}>
|
|
<Box>
|
|
<VehicleBookingQuickAddWidget />
|
|
</Box>
|
|
</Fade>
|
|
)}
|
|
|
|
{hasPermission('kalender:create') && widgetVisible('eventQuickAdd') && (
|
|
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '600ms' }}>
|
|
<Box>
|
|
<EventQuickAddWidget />
|
|
</Box>
|
|
</Fade>
|
|
)}
|
|
</WidgetGroup>
|
|
|
|
{/* Dienste Group */}
|
|
<WidgetGroup title="Dienste" gridColumn="1 / -1">
|
|
{hasPermission('wissen:widget_recent') && widgetVisible('bookstackRecent') && (
|
|
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '600ms' }}>
|
|
<Box>
|
|
<BookStackRecentWidget />
|
|
</Box>
|
|
</Fade>
|
|
)}
|
|
|
|
{hasPermission('wissen:widget_search') && widgetVisible('bookstackSearch') && (
|
|
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '640ms' }}>
|
|
<Box>
|
|
<BookStackSearchWidget />
|
|
</Box>
|
|
</Fade>
|
|
)}
|
|
|
|
{hasPermission('vikunja:widget_tasks') && widgetVisible('vikunjaTasks') && (
|
|
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '680ms' }}>
|
|
<Box>
|
|
<VikunjaMyTasksWidget />
|
|
</Box>
|
|
</Fade>
|
|
)}
|
|
|
|
{hasPermission('vikunja:widget_quick_add') && widgetVisible('vikunjaQuickAdd') && (
|
|
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '720ms' }}>
|
|
<Box>
|
|
<VikunjaQuickAddWidget />
|
|
</Box>
|
|
</Fade>
|
|
)}
|
|
</WidgetGroup>
|
|
|
|
{/* Information Group */}
|
|
<WidgetGroup title="Information" gridColumn="1 / -1">
|
|
{hasPermission('dashboard:widget_links') && widgetVisible('links') && linkCollections.map((collection, idx) => (
|
|
<Fade key={collection.id} in={!dataLoading} timeout={600} style={{ transitionDelay: `${760 + idx * 40}ms` }}>
|
|
<Box>
|
|
<LinksWidget collection={collection} />
|
|
</Box>
|
|
</Fade>
|
|
))}
|
|
|
|
{hasPermission('dashboard:widget_banner') && (
|
|
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: `${760 + linkCollections.length * 40}ms` }}>
|
|
<Box>
|
|
<BannerWidget />
|
|
</Box>
|
|
</Fade>
|
|
)}
|
|
</WidgetGroup>
|
|
</Box>
|
|
</Container>
|
|
</DashboardLayout>
|
|
);
|
|
}
|
|
|
|
export default Dashboard;
|