diff --git a/packages/core-service/src/core/settings/settings.controller.ts b/packages/core-service/src/core/settings/settings.controller.ts
index ed2ada8..e4b2a17 100644
--- a/packages/core-service/src/core/settings/settings.controller.ts
+++ b/packages/core-service/src/core/settings/settings.controller.ts
@@ -145,19 +145,23 @@ export class SettingsController {
async getBranding(): Promise<{
logo: string | null;
sidebarColor: string | null;
+ logoWidth: number | null;
+ sidebarWidth: number | null;
}> {
const raw = await this.redis.get(BRANDING_LOGO_KEY);
- if (!raw) return { logo: null, sidebarColor: null };
+ if (!raw) return { logo: null, sidebarColor: null, logoWidth: null, sidebarWidth: null };
try {
const data = JSON.parse(raw);
return {
- logo: data.logo || null,
+ logo: data.logo || null,
sidebarColor: data.sidebarColor || null,
+ logoWidth: typeof data.logoWidth === 'number' ? data.logoWidth : null,
+ sidebarWidth: typeof data.sidebarWidth === 'number' ? data.sidebarWidth : null,
};
} catch {
// Legacy: nur Logo als String
- return { logo: raw, sidebarColor: null };
+ return { logo: raw, sidebarColor: null, logoWidth: null, sidebarWidth: null };
}
}
@@ -170,15 +174,30 @@ export class SettingsController {
@UseGuards(RolesGuard)
@ApiOperation({ summary: 'Branding-Einstellungen speichern (Admin)' })
async saveBranding(
- @Body() body: { logo?: string | null; sidebarColor?: string | null },
+ @Body() body: {
+ logo?: string | null;
+ sidebarColor?: string | null;
+ logoWidth?: number | null;
+ sidebarWidth?: number | null;
+ },
): Promise<{ success: boolean }> {
if (body.logo && body.logo.length > 500_000) {
throw new BadRequestException('Logo darf maximal 500KB gross sein');
}
+ // Werte-Grenzen
+ const logoWidth = typeof body.logoWidth === 'number'
+ ? Math.min(Math.max(Math.round(body.logoWidth), 40), 240)
+ : null;
+ const sidebarWidth = typeof body.sidebarWidth === 'number'
+ ? Math.min(Math.max(Math.round(body.sidebarWidth), 200), 360)
+ : null;
+
const data = {
- logo: body.logo || null,
+ logo: body.logo || null,
sidebarColor: body.sidebarColor || null,
+ logoWidth,
+ sidebarWidth,
};
await this.redis.set(BRANDING_LOGO_KEY, JSON.stringify(data));
diff --git a/packages/frontend/src/admin/AdminCustomizePage.tsx b/packages/frontend/src/admin/AdminCustomizePage.tsx
index 32afa07..daf905d 100644
--- a/packages/frontend/src/admin/AdminCustomizePage.tsx
+++ b/packages/frontend/src/admin/AdminCustomizePage.tsx
@@ -5,6 +5,8 @@ import api from '../api/client';
interface BrandingData {
logo: string | null;
sidebarColor: string | null;
+ logoWidth: number | null;
+ sidebarWidth: number | null;
}
const SIDEBAR_PRESETS = [
@@ -63,6 +65,8 @@ export function AdminCustomizePage() {
const fileInputRef = useRef(null);
const [logo, setLogo] = useState(null);
const [sidebarColor, setSidebarColor] = useState('#1e293b');
+ const [logoWidth, setLogoWidth] = useState(160);
+ const [sidebarWidth, setSidebarWidth] = useState(240);
const [hasChanges, setHasChanges] = useState(false);
const [saveSuccess, setSaveSuccess] = useState(false);
@@ -78,6 +82,8 @@ export function AdminCustomizePage() {
if (data) {
setLogo(data.logo);
setSidebarColor(data.sidebarColor || '#1e293b');
+ setLogoWidth(data.logoWidth ?? 160);
+ setSidebarWidth(data.sidebarWidth ?? 240);
setHasChanges(false);
}
}, [data]);
@@ -86,6 +92,8 @@ export function AdminCustomizePage() {
mutationFn: async (branding: {
logo: string | null;
sidebarColor: string;
+ logoWidth: number;
+ sidebarWidth: number;
}) => {
const res = await api.post('/settings/branding', branding);
return res.data;
@@ -129,7 +137,7 @@ export function AdminCustomizePage() {
};
const handleSave = () => {
- saveMutation.mutate({ logo, sidebarColor });
+ saveMutation.mutate({ logo, sidebarColor, logoWidth, sidebarWidth });
};
return (
@@ -169,6 +177,7 @@ export function AdminCustomizePage() {
SVG mit transparentem Hintergrund, max. 500KB.
+ {/* Vorschau + Upload */}
{logo ? (
@@ -195,7 +205,7 @@ export function AdminCustomizePage() {
src={logo}
alt="Logo"
style={{
- maxWidth: '100%',
+ maxWidth: `${Math.round(logoWidth * 0.72)}px`,
maxHeight: '100%',
objectFit: 'contain',
}}
@@ -242,6 +252,29 @@ export function AdminCustomizePage() {
)}
+
+ {/* Logo-Breite Slider */}
+
+
+
{
+ setLogoWidth(Number(e.target.value));
+ setHasChanges(true);
+ }}
+ style={{ width: '100%', maxWidth: 320, display: 'block', marginBottom: '0.25rem' }}
+ />
+
+ 40px
+ 240px
+
+
{/* Sidebar-Farbe */}
@@ -414,6 +447,70 @@ export function AdminCustomizePage() {
+ {/* Sidebar-Breite */}
+
+
+ Sidebar-Breite
+
+
+ Breite der linken Menü-Leiste im ausgeklappten Zustand.
+
+
+ {/* Slider + Vorschau */}
+
+
+
+
{
+ setSidebarWidth(Number(e.target.value));
+ setHasChanges(true);
+ }}
+ style={{ width: '100%', maxWidth: 320, display: 'block', marginBottom: '0.25rem' }}
+ />
+
+ 200px (schmal)
+ 360px (breit)
+
+
+
+ {/* Sidebar-Vorschau skaliert */}
+
+ {/* Mini-Logo */}
+
+ {logo ? (
+

+ ) : 'INSIGHT'}
+
+ {/* Mini Nav-Einträge */}
+ {[80, 65, 75, 55].map((w, i) => (
+
+ ))}
+
+
+
+
{/* Speichern */}
({
queryKey: ['settings', 'branding'],
queryFn: async () => {
const res = await api.get<{
logo: string | null;
sidebarColor: string | null;
+ logoWidth: number | null;
+ sidebarWidth: number | null;
}>('/settings/branding');
return res.data;
},
@@ -172,11 +176,10 @@ export function AppLayout() {
{/* Sidebar */}
);