import React, { useState, useEffect } from 'react'; import { Container, Typography, Card, CardContent, Box, Divider, TextField, Button, IconButton, MenuItem, Select, FormControl, InputLabel, Stack, CircularProgress, Accordion, AccordionSummary, AccordionDetails, } from '@mui/material'; import { Delete, Add, Link as LinkIcon, Timer, Info, ExpandMore, PictureAsPdf as PdfIcon, } from '@mui/icons-material'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Navigate } from 'react-router-dom'; import DashboardLayout from '../components/dashboard/DashboardLayout'; import { useAuth } from '../contexts/AuthContext'; import { useNotification } from '../contexts/NotificationContext'; import { settingsApi } from '../services/settings'; interface LinkCollection { id: string; name: string; links: Array<{ name: string; url: string }>; } interface RefreshIntervals { dashboardWidgets: number; adminServices: number; } const DASHBOARD_INTERVAL_OPTIONS = [ { value: 30, label: '30 Sekunden' }, { value: 60, label: '1 Minute' }, { value: 300, label: '5 Minuten' }, { value: 600, label: '10 Minuten' }, ]; const ADMIN_INTERVAL_OPTIONS = [ { value: 5, label: '5 Sekunden' }, { value: 15, label: '15 Sekunden' }, { value: 30, label: '30 Sekunden' }, { value: 60, label: '60 Sekunden' }, ]; function AdminSettings() { const { user } = useAuth(); const { showSuccess, showError } = useNotification(); const queryClient = useQueryClient(); const isAdmin = user?.groups?.includes('dashboard_admin') ?? false; // State for link collections const [linkCollections, setLinkCollections] = useState([]); // State for refresh intervals const [refreshIntervals, setRefreshIntervals] = useState({ dashboardWidgets: 60, adminServices: 15, }); // State for PDF header/footer/logo/org const [pdfHeader, setPdfHeader] = useState(''); const [pdfFooter, setPdfFooter] = useState(''); const [pdfLogo, setPdfLogo] = useState(''); const [pdfOrgName, setPdfOrgName] = useState(''); // Fetch all settings const { data: settings, isLoading } = useQuery({ queryKey: ['admin-settings'], queryFn: () => settingsApi.getAll(), enabled: isAdmin, }); // Initialize state from fetched settings useEffect(() => { if (settings) { const linksSetting = settings.find((s) => s.key === 'external_links'); if (linksSetting?.value) { const val = linksSetting.value; if (Array.isArray(val) && val.length > 0 && 'links' in val[0]) { setLinkCollections(val); } else if (Array.isArray(val)) { // Old flat format — wrap in single collection setLinkCollections([{ id: 'default', name: 'Links', links: val }]); } } const intervalsSetting = settings.find((s) => s.key === 'refresh_intervals'); if (intervalsSetting?.value) { setRefreshIntervals({ dashboardWidgets: intervalsSetting.value.dashboardWidgets ?? 60, adminServices: intervalsSetting.value.adminServices ?? 15, }); } const pdfHeaderSetting = settings.find((s) => s.key === 'pdf_header'); if (pdfHeaderSetting?.value != null) setPdfHeader(pdfHeaderSetting.value); const pdfFooterSetting = settings.find((s) => s.key === 'pdf_footer'); if (pdfFooterSetting?.value != null) setPdfFooter(pdfFooterSetting.value); const pdfLogoSetting = settings.find((s) => s.key === 'pdf_logo'); if (pdfLogoSetting?.value != null) setPdfLogo(pdfLogoSetting.value); const pdfOrgNameSetting = settings.find((s) => s.key === 'pdf_org_name'); if (pdfOrgNameSetting?.value != null) setPdfOrgName(pdfOrgNameSetting.value); } }, [settings]); // Mutation for saving link collections const linksMutation = useMutation({ mutationFn: (collections: LinkCollection[]) => settingsApi.update('external_links', collections), onSuccess: () => { showSuccess('Externe Links gespeichert'); queryClient.invalidateQueries({ queryKey: ['admin-settings'] }); queryClient.invalidateQueries({ queryKey: ['external-links'] }); }, onError: () => { showError('Fehler beim Speichern der externen Links'); }, }); // Mutation for saving refresh intervals const intervalsMutation = useMutation({ mutationFn: (intervals: RefreshIntervals) => settingsApi.update('refresh_intervals', intervals), onSuccess: () => { showSuccess('Aktualisierungsintervalle gespeichert'); queryClient.invalidateQueries({ queryKey: ['admin-settings'] }); }, onError: () => { showError('Fehler beim Speichern der Aktualisierungsintervalle'); }, }); // Mutation for saving PDF settings const pdfHeaderMutation = useMutation({ mutationFn: (value: string) => settingsApi.update('pdf_header', value), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-settings'] }); queryClient.invalidateQueries({ queryKey: ['pdf-settings'] }); }, }); const pdfFooterMutation = useMutation({ mutationFn: (value: string) => settingsApi.update('pdf_footer', value), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-settings'] }); queryClient.invalidateQueries({ queryKey: ['pdf-settings'] }); }, }); const pdfLogoMutation = useMutation({ mutationFn: (value: string) => settingsApi.update('pdf_logo', value), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-settings'] }); queryClient.invalidateQueries({ queryKey: ['pdf-settings'] }); }, }); const pdfOrgNameMutation = useMutation({ mutationFn: (value: string) => settingsApi.update('pdf_org_name', value), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-settings'] }); queryClient.invalidateQueries({ queryKey: ['pdf-settings'] }); }, }); const handleSavePdfSettings = async () => { try { await Promise.all([ pdfHeaderMutation.mutateAsync(pdfHeader), pdfFooterMutation.mutateAsync(pdfFooter), pdfLogoMutation.mutateAsync(pdfLogo), pdfOrgNameMutation.mutateAsync(pdfOrgName), ]); showSuccess('PDF-Einstellungen gespeichert'); } catch { showError('Fehler beim Speichern der PDF-Einstellungen'); } }; const handleLogoUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = (ev) => setPdfLogo(ev.target?.result as string); reader.readAsDataURL(file); }; if (!isAdmin) { return ; } const handleAddCollection = () => { setLinkCollections([...linkCollections, { id: crypto.randomUUID(), name: '', links: [] }]); }; const handleRemoveCollection = (id: string) => { setLinkCollections(linkCollections.filter((c) => c.id !== id)); }; const handleCollectionNameChange = (id: string, name: string) => { setLinkCollections(linkCollections.map((c) => (c.id === id ? { ...c, name } : c))); }; const handleAddLink = (collectionId: string) => { setLinkCollections( linkCollections.map((c) => c.id === collectionId ? { ...c, links: [...c.links, { name: '', url: '' }] } : c ) ); }; const handleRemoveLink = (collectionId: string, linkIndex: number) => { setLinkCollections( linkCollections.map((c) => c.id === collectionId ? { ...c, links: c.links.filter((_, i) => i !== linkIndex) } : c ) ); }; const handleLinkChange = (collectionId: string, linkIndex: number, field: 'name' | 'url', value: string) => { setLinkCollections( linkCollections.map((c) => c.id === collectionId ? { ...c, links: c.links.map((l, i) => (i === linkIndex ? { ...l, [field]: value } : l)), } : c ) ); }; const handleSaveLinks = () => { linksMutation.mutate(linkCollections); }; const handleSaveIntervals = () => { intervalsMutation.mutate(refreshIntervals); }; // Find the most recent updated_at const lastUpdated = settings?.reduce((latest, s) => { if (!latest) return s.updated_at; return new Date(s.updated_at) > new Date(latest) ? s.updated_at : latest; }, '' as string); if (isLoading) { return ( ); } return ( Admin-Einstellungen {/* Section 1: Link Collections */} FF Rems Tools — Link-Sammlungen {linkCollections.map((collection) => ( }> handleCollectionNameChange(collection.id, e.target.value)} size="small" sx={{ flex: 1 }} onClick={(e) => e.stopPropagation()} /> { e.stopPropagation(); handleRemoveCollection(collection.id); }} aria-label="Sammlung entfernen" size="small" > {collection.links.map((link, linkIndex) => ( handleLinkChange(collection.id, linkIndex, 'name', e.target.value)} size="small" sx={{ flex: 1 }} /> handleLinkChange(collection.id, linkIndex, 'url', e.target.value)} size="small" sx={{ flex: 2 }} /> handleRemoveLink(collection.id, linkIndex)} aria-label="Link entfernen" > ))} ))} {/* Section 2: Refresh Intervals */} Aktualisierungsintervalle Dashboard Widgets Admin Services {/* Section 3: PDF Settings */} PDF-Einstellungen Kopf- und Fußzeile für Kalender-PDF-Exporte. Zeilenumbrüche werden übernommen, **fett** erzeugt fettgedruckten Text. setPdfOrgName(e.target.value)} fullWidth size="small" placeholder="FREIWILLIGE FEUERWEHR REMS" /> Logo (erscheint rechts im PDF-Header, neben dem Organisationsnamen) {pdfLogo && ( <> setPdfLogo('')} title="Logo entfernen"> )} setPdfHeader(e.target.value)} multiline minRows={2} maxRows={6} fullWidth size="small" /> setPdfFooter(e.target.value)} multiline minRows={2} maxRows={6} fullWidth size="small" /> {/* Section 4: Info */} Info {lastUpdated ? `Letzte Aktualisierung: ${new Date(lastUpdated).toLocaleString('de-DE')}` : 'Noch keine Einstellungen gespeichert.'} ); } export default AdminSettings;