import React, { useMemo } from 'react'; import { NavLink, Outlet } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { usePluginStore } from '@/store/pluginStore'; import { useUIStore } from '@/store/uiStore'; import { usePermission } from '@/hooks/usePermission'; import { useAuthStore } from '@/store/authStore'; import { Settings, Mail, Bell, Sparkles, Bot, Shield, Users, UsersRound, Package, ArrowLeft, FileText } from 'lucide-react'; const ICON_MAP: Record> = { Settings, Mail, Bell, Sparkles, Bot, Shield, Users, UsersRound, FileText, }; const FALLBACK_ICON = Package; export function SettingsPage() { const { t } = useTranslation(); const manifests = usePluginStore(s => s.manifests); const sidebarOpen = useUIStore((state) => state.sidebarOpen); const { hasPermission } = usePermission(); const user = useAuthStore((state) => state.user); const pluginSettingsPages = useMemo( () => (manifests || []) .flatMap((m) => (Array.isArray(m.settings_pages) ? m.settings_pages : [])) .filter((p): p is NonNullable => !!p && !!p.path) // ARCH-006 parity: hide settings pages the user has no permission for // (fail-closed while permissions are loading) .filter(p => !p.permission || (user?.is_system_admin || hasPermission(p.permission))) .sort((a, b) => a.order - b.order), [manifests, user] ); // W3b (#358): Only true core settings — plugin settings come exclusively // via pluginNavItems (settings_pages contributions). No duplication. const hardcodedNavItems = [ { to: '/settings/stammdaten', label: 'Stammdaten', icon: '\ud83c\udfe2' }, { to: '/settings/user-management', label: 'Nutzerverwaltung', icon: '\ud83d\udc65' }, { to: '/settings/system', label: 'System', icon: '\u2699\ufe0f' }, { to: '/settings/custom-fields', label: 'Custom Fields', icon: '\ud83d\udccb' }, { to: '/settings/webhooks', label: 'Webhooks', icon: '\ud83d\udd14' }, { to: '/settings/workspaces', label: 'Workspaces', icon: '\ud83d\udd58\ufe0f' }, { to: '/settings/backup', label: 'Backup & Restore', icon: '\ud83d\udcbe' }, { to: '/settings/api-tokens', label: 'API-Tokens', icon: '\ud83d\udd11' }, { to: '/settings/tenants', label: 'Mandanten', icon: '\ud83c\udfe2' }, { to: '/settings/permission-templates', label: 'Berechtigungs-Vorlagen', icon: '\ud83d\udd11' }, ]; const existingPaths = new Set(hardcodedNavItems.map(item => item.to)); // Path-based dedup only — the old label-based hack hid legitimate plugin // pages whenever their English label collided with a hardcoded one. const pluginNavItems = pluginSettingsPages .filter(p => !existingPaths.has(`/settings/${p.path}`)) .map(p => ({ to: `/settings/${p.path}`, label: t(p.label_key, p.label), icon: (() => { const Icon = ICON_MAP[p.icon] ?? FALLBACK_ICON; return React.createElement(Icon, { className: 'w-4 h-4' }); })(), })); const navItems = [...hardcodedNavItems, ...pluginNavItems]; return (
); }