This commit is contained in:
Matthias Hochmeister
2026-03-26 09:29:59 +01:00
parent 884397b520
commit d5e5f2d44e
10 changed files with 428 additions and 154 deletions

View File

@@ -27,6 +27,7 @@ import {
Info,
ExpandMore,
PictureAsPdf as PdfIcon,
Settings as SettingsIcon,
} from '@mui/icons-material';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { Navigate } from 'react-router-dom';
@@ -82,6 +83,9 @@ function AdminSettings() {
const [pdfLogo, setPdfLogo] = useState('');
const [pdfOrgName, setPdfOrgName] = useState('');
// State for app logo
const [appLogo, setAppLogo] = useState('');
// Fetch all settings
const { data: settings, isLoading } = useQuery({
queryKey: ['admin-settings'],
@@ -119,6 +123,8 @@ function AdminSettings() {
if (pdfLogoSetting?.value != null) setPdfLogo(pdfLogoSetting.value);
const pdfOrgNameSetting = settings.find((s) => s.key === 'pdf_org_name');
if (pdfOrgNameSetting?.value != null) setPdfOrgName(pdfOrgNameSetting.value);
const appLogoSetting = settings.find((s) => s.key === 'app_logo');
if (appLogoSetting?.value != null) setAppLogo(appLogoSetting.value);
}
}, [settings]);
@@ -197,6 +203,30 @@ function AdminSettings() {
reader.readAsDataURL(file);
};
// App logo mutation + handlers
const appLogoMutation = useMutation({
mutationFn: (value: string) => settingsApi.update('app_logo', value),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin-settings'] });
queryClient.invalidateQueries({ queryKey: ['pdf-settings'] });
},
});
const handleAppLogoUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (ev) => setAppLogo(ev.target?.result as string);
reader.readAsDataURL(file);
};
const handleSaveAppLogo = async () => {
try {
await appLogoMutation.mutateAsync(appLogo);
showSuccess('Logo gespeichert');
} catch {
showError('Fehler beim Speichern des Logos');
}
};
if (!canAccess) {
return <Navigate to="/dashboard" replace />;
}
@@ -276,7 +306,59 @@ function AdminSettings() {
</Typography>
<Stack spacing={3}>
{/* Section 1: Link Collections */}
{/* Section 1: General Settings (App Logo) */}
<Card>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<SettingsIcon color="primary" sx={{ mr: 2 }} />
<Typography variant="h6">Allgemeine Einstellungen</Typography>
</Box>
<Divider sx={{ mb: 2 }} />
<Stack spacing={2}>
<Box>
<Typography variant="body2" color="text.secondary" gutterBottom>
Logo (wird im Header und in PDF-Exporten verwendet)
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Button component="label" variant="outlined" size="small">
Logo hochladen
<input
type="file"
hidden
accept="image/*"
onChange={handleAppLogoUpload}
/>
</Button>
{appLogo && (
<>
<Box
component="img"
src={appLogo}
alt="Logo Vorschau"
sx={{ height: 40, maxWidth: 120, objectFit: 'contain', borderRadius: 1 }}
/>
<IconButton size="small" onClick={() => setAppLogo('')} title="Logo entfernen">
<Delete fontSize="small" />
</IconButton>
</>
)}
</Box>
</Box>
<Box>
<Button
onClick={handleSaveAppLogo}
variant="contained"
size="small"
disabled={appLogoMutation.isPending}
>
Speichern
</Button>
</Box>
</Stack>
</CardContent>
</Card>
{/* Section 2: Link Collections */}
<Card>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
@@ -375,7 +457,7 @@ function AdminSettings() {
</CardContent>
</Card>
{/* Section 2: Refresh Intervals */}
{/* Section 3: Refresh Intervals */}
<Card>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
@@ -439,7 +521,7 @@ function AdminSettings() {
</CardContent>
</Card>
{/* Section 3: PDF Settings */}
{/* Section 4: PDF Settings */}
<Card>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
@@ -459,35 +541,6 @@ function AdminSettings() {
size="small"
placeholder="FREIWILLIGE FEUERWEHR REMS"
/>
<Box>
<Typography variant="body2" color="text.secondary" gutterBottom>
Logo (erscheint rechts im PDF-Header, neben dem Organisationsnamen)
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Button component="label" variant="outlined" size="small">
Logo hochladen
<input
type="file"
hidden
accept="image/*"
onChange={handleLogoUpload}
/>
</Button>
{pdfLogo && (
<>
<Box
component="img"
src={pdfLogo}
alt="Logo Vorschau"
sx={{ height: 40, maxWidth: 120, objectFit: 'contain', borderRadius: 1 }}
/>
<IconButton size="small" onClick={() => setPdfLogo('')} title="Logo entfernen">
<Delete fontSize="small" />
</IconButton>
</>
)}
</Box>
</Box>
<TextField
label="PDF Kopfzeile (links, unter dem Header-Banner)"
value={pdfHeader}
@@ -527,7 +580,7 @@ function AdminSettings() {
</CardContent>
</Card>
{/* Section 4: Info */}
{/* Section 5: Info */}
<Card>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>