annoucement banners, calendar pdf export, vehicle booking quck-add, even quick-add

This commit is contained in:
Matthias Hochmeister
2026-03-12 11:47:08 +01:00
parent 71a04aee89
commit cd68bd3795
15 changed files with 997 additions and 0 deletions

View File

@@ -6,6 +6,7 @@ import ServiceManagerTab from '../components/admin/ServiceManagerTab';
import SystemHealthTab from '../components/admin/SystemHealthTab';
import UserOverviewTab from '../components/admin/UserOverviewTab';
import NotificationBroadcastTab from '../components/admin/NotificationBroadcastTab';
import BannerManagementTab from '../components/admin/BannerManagementTab';
import { useAuth } from '../contexts/AuthContext';
interface TabPanelProps {
@@ -39,6 +40,7 @@ function AdminDashboard() {
<Tab label="System" />
<Tab label="Benutzer" />
<Tab label="Broadcast" />
<Tab label="Banner" />
</Tabs>
</Box>
@@ -54,6 +56,9 @@ function AdminDashboard() {
<TabPanel value={tab} index={3}>
<NotificationBroadcastTab />
</TabPanel>
<TabPanel value={tab} index={4}>
<BannerManagementTab />
</TabPanel>
</DashboardLayout>
);
}

View File

@@ -19,12 +19,18 @@ import VikunjaMyTasksWidget from '../components/dashboard/VikunjaMyTasksWidget';
import VikunjaQuickAddWidget from '../components/dashboard/VikunjaQuickAddWidget';
import VikunjaOverdueNotifier from '../components/dashboard/VikunjaOverdueNotifier';
import AdminStatusWidget from '../components/dashboard/AdminStatusWidget';
import AnnouncementBanner from '../components/dashboard/AnnouncementBanner';
import VehicleBookingQuickAddWidget from '../components/dashboard/VehicleBookingQuickAddWidget';
import EventQuickAddWidget from '../components/dashboard/EventQuickAddWidget';
function Dashboard() {
const { user } = useAuth();
const isAdmin = user?.groups?.includes('dashboard_admin') ?? false;
const canViewAtemschutz = user?.groups?.some(g =>
['dashboard_admin', 'dashboard_kommando', 'dashboard_atemschutz', 'dashboard_moderator'].includes(g)
) ?? false;
const canWrite = user?.groups?.some(g =>
['dashboard_admin', 'dashboard_kommando', 'dashboard_moderator', 'dashboard_gruppenfuehrer'].includes(g)
) ?? false;
const [dataLoading, setDataLoading] = useState(true);
useEffect(() => {
@@ -38,6 +44,7 @@ function Dashboard() {
return (
<DashboardLayout>
<Container maxWidth={false} disableGutters>
<AnnouncementBanner />
<Box
sx={{
display: 'grid',
@@ -148,6 +155,28 @@ function Dashboard() {
</Fade>
</Box>
{/* Vehicle Booking — Quick Add Widget */}
{canWrite && (
<Box>
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '720ms' }}>
<Box>
<VehicleBookingQuickAddWidget />
</Box>
</Fade>
</Box>
)}
{/* Event — Quick Add Widget */}
{canWrite && (
<Box>
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '760ms' }}>
<Box>
<EventQuickAddWidget />
</Box>
</Fade>
</Box>
)}
{/* Vikunja — Overdue Notifier (invisible, polling component) */}
<VikunjaOverdueNotifier />

View File

@@ -693,6 +693,70 @@ async function generatePdf(
doc.save(filename);
}
// ──────────────────────────────────────────────────────────────────────────────
// PDF Export — Fahrzeugbuchungen
// ──────────────────────────────────────────────────────────────────────────────
async function generateBookingsPdf(
weekStart: Date,
weekEnd: Date,
bookings: FahrzeugBuchungListItem[],
) {
const { jsPDF } = await import('jspdf');
const autoTable = (await import('jspdf-autotable')).default;
const doc = new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'a4' });
const startLabel = fnsFormat(weekStart, 'dd.MM.yyyy');
const endLabel = fnsFormat(weekEnd, 'dd.MM.yyyy');
const kwLabel = `KW ${fnsFormat(weekStart, 'w')}`;
// Header bar
doc.setFillColor(183, 28, 28); // fire-red
doc.rect(0, 0, 297, 18, 'F');
doc.setTextColor(255, 255, 255);
doc.setFontSize(14);
doc.setFont('helvetica', 'bold');
doc.text(`Fahrzeugbuchungen — ${kwLabel} · ${startLabel} ${endLabel}`, 10, 12);
doc.setFontSize(9);
doc.setFont('helvetica', 'normal');
doc.text('Feuerwehr Rems', 250, 12);
const formatDt = (iso: string) => {
const d = new Date(iso);
return fnsFormat(d, 'dd.MM.yyyy HH:mm');
};
const active = bookings.filter((b) => !b.abgesagt);
const rows = active.map((b) => [
b.fahrzeug_name + (b.fahrzeug_kennzeichen ? `\n${b.fahrzeug_kennzeichen}` : ''),
b.titel,
formatDt(b.beginn),
formatDt(b.ende),
BUCHUNGS_ART_LABELS[b.buchungs_art],
]);
autoTable(doc, {
head: [['Fahrzeug', 'Titel', 'Beginn', 'Ende', 'Art']],
body: rows,
startY: 22,
headStyles: { fillColor: [183, 28, 28], textColor: 255, fontStyle: 'bold' },
alternateRowStyles: { fillColor: [250, 235, 235] },
margin: { left: 10, right: 10 },
styles: { fontSize: 9, cellPadding: 2 },
columnStyles: {
0: { cellWidth: 45 },
1: { cellWidth: 90 },
2: { cellWidth: 38 },
3: { cellWidth: 38 },
4: { cellWidth: 35 },
},
});
const filename = `fahrzeugbuchungen_${fnsFormat(weekStart, 'yyyy-MM-dd')}.pdf`;
doc.save(filename);
}
// ──────────────────────────────────────────────────────────────────────────────
// CSV Import Dialog
// ──────────────────────────────────────────────────────────────────────────────
@@ -2303,6 +2367,16 @@ export default function Kalender() {
>
Kalender
</Button>
{/* PDF Export */}
<Tooltip title="PDF exportieren">
<IconButton
size="small"
onClick={() => generateBookingsPdf(currentWeekStart, weekEnd, bookings)}
>
<FileDownloadIcon />
</IconButton>
</Tooltip>
</Box>
{bookingsLoading && (