resolve issues with new features
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Container,
|
||||
Typography,
|
||||
@@ -76,9 +76,11 @@ function AdminSettings() {
|
||||
adminServices: 15,
|
||||
});
|
||||
|
||||
// State for PDF header/footer
|
||||
// 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({
|
||||
@@ -113,6 +115,10 @@ function AdminSettings() {
|
||||
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]);
|
||||
|
||||
@@ -156,17 +162,40 @@ function AdminSettings() {
|
||||
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<HTMLInputElement>) => {
|
||||
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 <Navigate to="/dashboard" replace />;
|
||||
@@ -423,7 +452,44 @@ function AdminSettings() {
|
||||
</Typography>
|
||||
<Stack spacing={2}>
|
||||
<TextField
|
||||
label="PDF Kopfzeile"
|
||||
label="Organisationsname (rechts im PDF-Header)"
|
||||
value={pdfOrgName}
|
||||
onChange={(e) => setPdfOrgName(e.target.value)}
|
||||
fullWidth
|
||||
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}
|
||||
onChange={(e) => setPdfHeader(e.target.value)}
|
||||
multiline
|
||||
@@ -447,7 +513,12 @@ function AdminSettings() {
|
||||
onClick={handleSavePdfSettings}
|
||||
variant="contained"
|
||||
size="small"
|
||||
disabled={pdfHeaderMutation.isPending || pdfFooterMutation.isPending}
|
||||
disabled={
|
||||
pdfHeaderMutation.isPending ||
|
||||
pdfFooterMutation.isPending ||
|
||||
pdfLogoMutation.isPending ||
|
||||
pdfOrgNameMutation.isPending
|
||||
}
|
||||
>
|
||||
Speichern
|
||||
</Button>
|
||||
|
||||
@@ -398,7 +398,7 @@ function FahrzeugBuchungen() {
|
||||
<TableContainer component={Paper} elevation={1}>
|
||||
<Table size="small" sx={{ tableLayout: 'fixed' }}>
|
||||
<TableHead>
|
||||
<TableRow sx={{ bgcolor: (theme) => theme.palette.mode === 'dark' ? 'grey.800' : 'grey.100' }}>
|
||||
<TableRow sx={{ bgcolor: (theme) => theme.palette.mode === 'dark' ? 'grey.900' : 'grey.100' }}>
|
||||
<TableCell sx={{ width: 160, fontWeight: 700 }}>
|
||||
Fahrzeug
|
||||
</TableCell>
|
||||
|
||||
@@ -675,7 +675,7 @@ async function fetchPdfSettings(): Promise<PdfSettings> {
|
||||
_pdfSettingsCacheTime = Date.now();
|
||||
return _pdfSettingsCache;
|
||||
} catch {
|
||||
return { pdf_header: '', pdf_footer: '' };
|
||||
return { pdf_header: '', pdf_footer: '', pdf_logo: '', pdf_org_name: '' };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -701,9 +701,28 @@ async function generatePdf(
|
||||
doc.setFontSize(14);
|
||||
doc.setFont('helvetica', 'bold');
|
||||
doc.text(`Kalender — ${monthLabel} ${year}`, 10, 12);
|
||||
doc.setFontSize(9);
|
||||
doc.setFont('helvetica', 'normal');
|
||||
doc.text('Feuerwehr Rems', 250, 12);
|
||||
|
||||
// Right side: logo and/or org name
|
||||
const logoSize = 14;
|
||||
const logoX = 297 - 4 - logoSize; // 4mm right margin
|
||||
if (pdfSettings.pdf_logo) {
|
||||
try {
|
||||
const fmt = pdfSettings.pdf_logo.match(/^data:image\/(\w+);/)?.[1]?.toUpperCase() ?? 'PNG';
|
||||
doc.addImage(pdfSettings.pdf_logo, fmt === 'JPG' ? 'JPEG' : fmt, logoX, 2, logoSize, logoSize);
|
||||
} catch { /* ignore invalid image */ }
|
||||
}
|
||||
if (pdfSettings.pdf_org_name) {
|
||||
doc.setFontSize(9);
|
||||
doc.setFont('helvetica', 'bold');
|
||||
doc.setTextColor(255, 255, 255);
|
||||
const nameW = doc.getTextWidth(pdfSettings.pdf_org_name);
|
||||
const nameX = (pdfSettings.pdf_logo ? logoX - 3 : 297 - 4) - nameW;
|
||||
doc.text(pdfSettings.pdf_org_name, nameX, 12);
|
||||
} else if (!pdfSettings.pdf_logo) {
|
||||
doc.setFontSize(9);
|
||||
doc.setFont('helvetica', 'normal');
|
||||
doc.text('Feuerwehr Rems', 250, 12);
|
||||
}
|
||||
|
||||
// Custom header text
|
||||
let tableStartY = 22;
|
||||
@@ -796,9 +815,28 @@ async function generateBookingsPdf(
|
||||
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);
|
||||
|
||||
// Right side: logo and/or org name
|
||||
const logoSize = 14;
|
||||
const logoX = 297 - 4 - logoSize;
|
||||
if (pdfSettings.pdf_logo) {
|
||||
try {
|
||||
const fmt = pdfSettings.pdf_logo.match(/^data:image\/(\w+);/)?.[1]?.toUpperCase() ?? 'PNG';
|
||||
doc.addImage(pdfSettings.pdf_logo, fmt === 'JPG' ? 'JPEG' : fmt, logoX, 2, logoSize, logoSize);
|
||||
} catch { /* ignore invalid image */ }
|
||||
}
|
||||
if (pdfSettings.pdf_org_name) {
|
||||
doc.setFontSize(9);
|
||||
doc.setFont('helvetica', 'bold');
|
||||
doc.setTextColor(255, 255, 255);
|
||||
const nameW = doc.getTextWidth(pdfSettings.pdf_org_name);
|
||||
const nameX = (pdfSettings.pdf_logo ? logoX - 3 : 297 - 4) - nameW;
|
||||
doc.text(pdfSettings.pdf_org_name, nameX, 12);
|
||||
} else if (!pdfSettings.pdf_logo) {
|
||||
doc.setFontSize(9);
|
||||
doc.setFont('helvetica', 'normal');
|
||||
doc.text('Feuerwehr Rems', 250, 12);
|
||||
}
|
||||
|
||||
// Custom header text
|
||||
let tableStartY = 22;
|
||||
@@ -2747,7 +2785,7 @@ export default function Kalender() {
|
||||
<TableContainer component={Paper} elevation={1}>
|
||||
<Table size="small" sx={{ tableLayout: 'fixed' }}>
|
||||
<TableHead>
|
||||
<TableRow sx={{ bgcolor: 'grey.100' }}>
|
||||
<TableRow sx={{ bgcolor: (theme) => theme.palette.mode === 'dark' ? 'grey.900' : 'grey.100' }}>
|
||||
<TableCell sx={{ width: 160, fontWeight: 700 }}>
|
||||
Fahrzeug
|
||||
</TableCell>
|
||||
@@ -2758,7 +2796,7 @@ export default function Kalender() {
|
||||
sx={{
|
||||
fontWeight: fnsIsToday(day) ? 700 : 400,
|
||||
color: fnsIsToday(day) ? 'primary.main' : 'text.primary',
|
||||
bgcolor: fnsIsToday(day) ? 'primary.50' : undefined,
|
||||
bgcolor: fnsIsToday(day) ? (theme) => theme.palette.mode === 'dark' ? 'primary.900' : 'primary.50' : undefined,
|
||||
}}
|
||||
>
|
||||
<Typography variant="caption" display="block">
|
||||
|
||||
Reference in New Issue
Block a user