resolve issues with new features
This commit is contained in:
318
frontend/src/pages/AdminSettings.tsx
Normal file
318
frontend/src/pages/AdminSettings.tsx
Normal file
@@ -0,0 +1,318 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
Container,
|
||||
Typography,
|
||||
Card,
|
||||
CardContent,
|
||||
Box,
|
||||
Divider,
|
||||
TextField,
|
||||
Button,
|
||||
IconButton,
|
||||
MenuItem,
|
||||
Select,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Stack,
|
||||
CircularProgress,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Delete,
|
||||
Add,
|
||||
Link as LinkIcon,
|
||||
Timer,
|
||||
Info,
|
||||
} 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 ExternalLink {
|
||||
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 external links
|
||||
const [externalLinks, setExternalLinks] = useState<ExternalLink[]>([]);
|
||||
|
||||
// State for refresh intervals
|
||||
const [refreshIntervals, setRefreshIntervals] = useState<RefreshIntervals>({
|
||||
dashboardWidgets: 60,
|
||||
adminServices: 15,
|
||||
});
|
||||
|
||||
// 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) {
|
||||
setExternalLinks(linksSetting.value);
|
||||
}
|
||||
|
||||
const intervalsSetting = settings.find((s) => s.key === 'refresh_intervals');
|
||||
if (intervalsSetting?.value) {
|
||||
setRefreshIntervals({
|
||||
dashboardWidgets: intervalsSetting.value.dashboardWidgets ?? 60,
|
||||
adminServices: intervalsSetting.value.adminServices ?? 15,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [settings]);
|
||||
|
||||
// Mutation for saving external links
|
||||
const linksMutation = useMutation({
|
||||
mutationFn: (links: ExternalLink[]) => settingsApi.update('external_links', links),
|
||||
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');
|
||||
},
|
||||
});
|
||||
|
||||
if (!isAdmin) {
|
||||
return <Navigate to="/dashboard" replace />;
|
||||
}
|
||||
|
||||
const handleAddLink = () => {
|
||||
setExternalLinks([...externalLinks, { name: '', url: '' }]);
|
||||
};
|
||||
|
||||
const handleRemoveLink = (index: number) => {
|
||||
setExternalLinks(externalLinks.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleLinkChange = (index: number, field: keyof ExternalLink, value: string) => {
|
||||
const updated = [...externalLinks];
|
||||
updated[index] = { ...updated[index], [field]: value };
|
||||
setExternalLinks(updated);
|
||||
};
|
||||
|
||||
const handleSaveLinks = () => {
|
||||
linksMutation.mutate(externalLinks);
|
||||
};
|
||||
|
||||
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 (
|
||||
<DashboardLayout>
|
||||
<Container maxWidth="lg">
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 8 }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
</Container>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<Container maxWidth="lg">
|
||||
<Typography variant="h4" gutterBottom sx={{ mb: 4 }}>
|
||||
Admin-Einstellungen
|
||||
</Typography>
|
||||
|
||||
<Stack spacing={3}>
|
||||
{/* Section 1: External Links */}
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<LinkIcon color="primary" sx={{ mr: 2 }} />
|
||||
<Typography variant="h6">FF Rems Tools — Externe Links</Typography>
|
||||
</Box>
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
|
||||
<Stack spacing={2}>
|
||||
{externalLinks.map((link, index) => (
|
||||
<Box key={index} sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
|
||||
<TextField
|
||||
label="Name"
|
||||
value={link.name}
|
||||
onChange={(e) => handleLinkChange(index, 'name', e.target.value)}
|
||||
size="small"
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
<TextField
|
||||
label="URL"
|
||||
value={link.url}
|
||||
onChange={(e) => handleLinkChange(index, 'url', e.target.value)}
|
||||
size="small"
|
||||
sx={{ flex: 2 }}
|
||||
/>
|
||||
<IconButton
|
||||
color="error"
|
||||
onClick={() => handleRemoveLink(index)}
|
||||
aria-label="Link entfernen"
|
||||
>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
<Button
|
||||
startIcon={<Add />}
|
||||
onClick={handleAddLink}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
>
|
||||
Link hinzufügen
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSaveLinks}
|
||||
variant="contained"
|
||||
size="small"
|
||||
disabled={linksMutation.isPending}
|
||||
>
|
||||
Speichern
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Section 2: Refresh Intervals */}
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<Timer color="primary" sx={{ mr: 2 }} />
|
||||
<Typography variant="h6">Aktualisierungsintervalle</Typography>
|
||||
</Box>
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
|
||||
<Stack spacing={3}>
|
||||
<FormControl size="small" sx={{ maxWidth: 300 }}>
|
||||
<InputLabel>Dashboard Widgets</InputLabel>
|
||||
<Select
|
||||
value={refreshIntervals.dashboardWidgets}
|
||||
label="Dashboard Widgets"
|
||||
onChange={(e) =>
|
||||
setRefreshIntervals((prev) => ({
|
||||
...prev,
|
||||
dashboardWidgets: Number(e.target.value),
|
||||
}))
|
||||
}
|
||||
>
|
||||
{DASHBOARD_INTERVAL_OPTIONS.map((opt) => (
|
||||
<MenuItem key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl size="small" sx={{ maxWidth: 300 }}>
|
||||
<InputLabel>Admin Services</InputLabel>
|
||||
<Select
|
||||
value={refreshIntervals.adminServices}
|
||||
label="Admin Services"
|
||||
onChange={(e) =>
|
||||
setRefreshIntervals((prev) => ({
|
||||
...prev,
|
||||
adminServices: Number(e.target.value),
|
||||
}))
|
||||
}
|
||||
>
|
||||
{ADMIN_INTERVAL_OPTIONS.map((opt) => (
|
||||
<MenuItem key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<Box>
|
||||
<Button
|
||||
onClick={handleSaveIntervals}
|
||||
variant="contained"
|
||||
size="small"
|
||||
disabled={intervalsMutation.isPending}
|
||||
>
|
||||
Speichern
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Section 3: Info */}
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<Info color="primary" sx={{ mr: 2 }} />
|
||||
<Typography variant="h6">Info</Typography>
|
||||
</Box>
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{lastUpdated
|
||||
? `Letzte Aktualisierung: ${new Date(lastUpdated).toLocaleString('de-DE')}`
|
||||
: 'Noch keine Einstellungen gespeichert.'}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Stack>
|
||||
</Container>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default AdminSettings;
|
||||
Reference in New Issue
Block a user