From b7f2e2adbeacadeaaa0e12fcc5eab02ac113b434 Mon Sep 17 00:00:00 2001 From: Lars Date: Sun, 5 Apr 2026 11:05:45 +0200 Subject: [PATCH] feat: Add Admin Group Hub page and update navigation structure for improved admin management --- frontend/src/App.jsx | 2 + frontend/src/app.css | 120 +------------------ frontend/src/config/adminNav.js | 139 +++++++++++++++++++---- frontend/src/layouts/AdminShell.jsx | 67 ++++++----- frontend/src/pages/AdminGroupHubPage.jsx | 57 ++++++++++ frontend/src/pages/AdminHomePage.jsx | 51 +++++---- frontend/src/pages/Analysis.jsx | 2 +- 7 files changed, 247 insertions(+), 191 deletions(-) create mode 100644 frontend/src/pages/AdminGroupHubPage.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 146477a..332cad7 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -37,6 +37,7 @@ import AdminFocusAreasPage from './pages/AdminFocusAreasPage' import AdminHomePage from './pages/AdminHomePage' import AdminUsersPage from './pages/AdminUsersPage' import AdminSystemPage from './pages/AdminSystemPage' +import AdminGroupHubPage from './pages/AdminGroupHubPage' import RequireAdmin from './layouts/RequireAdmin' import AdminShell from './layouts/AdminShell' import SubscriptionPage from './pages/SubscriptionPage' @@ -227,6 +228,7 @@ function AppShell() { }> }> } /> + } /> } /> } /> }/> diff --git a/frontend/src/app.css b/frontend/src/app.css index 2b8a70f..1f54390 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -304,6 +304,11 @@ body { font-family: var(--font); background: var(--bg); color: var(--text1); -we opacity: 1; } +a.analysis-split__nav-item { + text-decoration: none; + box-sizing: border-box; +} + .analysis-split__main { min-width: 0; } @@ -474,90 +479,11 @@ body { font-family: var(--font); background: var(--bg); color: var(--text1); -we } } -/* Admin: gruppierte Sub-Navigation (Mobil = Chips pro Gruppe, Desktop = linke Spalte) */ +/* Admin: Split-Layout wie .analysis-split (nur Gruppen in der Nav) */ .admin-shell { width: 100%; } -.admin-shell__layout { - display: flex; - flex-direction: column; - gap: 16px; -} - -.admin-shell__nav-groups { - display: flex; - flex-direction: column; - gap: 14px; -} - -.admin-shell__nav-group { - display: flex; - flex-direction: column; - gap: 6px; -} - -.admin-shell__group-title { - font-size: 11px; - font-weight: 700; - letter-spacing: 0.04em; - text-transform: uppercase; - color: var(--text3); - padding: 0 2px; -} - -.admin-shell__group-chips { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - gap: 6px; - overflow-x: auto; - padding-bottom: 4px; - -ms-overflow-style: none; - scrollbar-width: none; -} - -.admin-shell__group-chips::-webkit-scrollbar { - display: none; -} - -.admin-shell__nav-item { - flex-shrink: 0; - display: inline-flex; - align-items: center; - justify-content: center; - padding: 7px 12px; - border-radius: 20px; - border: 1.5px solid var(--border2); - background: var(--surface); - color: var(--text2); - font-family: var(--font); - font-size: 13px; - font-weight: 500; - text-decoration: none; - white-space: nowrap; - box-sizing: border-box; -} - -.admin-shell__nav-item:hover { - border-color: var(--accent); - color: var(--text1); -} - -.admin-shell__nav-item--active { - border-color: var(--accent); - background: var(--accent); - color: white; -} - -.admin-shell__nav-item--active:hover { - color: white; -} - -.admin-shell__main { - min-width: 0; -} - .admin-page { width: 100%; } @@ -568,40 +494,6 @@ body { font-family: var(--font); background: var(--bg); color: var(--text1); -we margin-left: auto; margin-right: auto; } - - .admin-shell__layout { - flex-direction: row; - align-items: flex-start; - gap: 24px; - } - - .admin-shell__nav-wrap { - flex: 0 0 260px; - max-width: 280px; - position: sticky; - top: 16px; - align-self: flex-start; - } - - .admin-shell__group-chips { - flex-direction: column; - overflow-x: visible; - overflow-y: visible; - padding-bottom: 0; - gap: 6px; - } - - .admin-shell__nav-item { - width: 100%; - justify-content: flex-start; - border-radius: 10px; - white-space: normal; - padding: 9px 12px; - } - - .admin-shell__main { - flex: 1; - } } .muted { color: var(--text3); font-size: 13px; } diff --git a/frontend/src/config/adminNav.js b/frontend/src/config/adminNav.js index 49c76de..80da080 100644 --- a/frontend/src/config/adminNav.js +++ b/frontend/src/config/adminNav.js @@ -1,61 +1,154 @@ /** - * Gruppierte Admin-Navigation (Shell: Chips mobil, Seitenleiste Desktop). - * @typedef {{ to: string, label: string, end?: boolean }} AdminNavItem - * @typedef {{ id: string, label: string, items: AdminNavItem[] }} AdminNavGroup + * Admin: Nur Gruppen in der Shell-Navigation; konkrete Seiten wählt man auf der Hub-Seite (/admin/g/:id). + * @typedef {{ to: string, label: string, description?: string }} AdminGroupItem + * @typedef {{ id: string, label: string, description: string, items: AdminGroupItem[] }} AdminGroup */ -/** @type {AdminNavGroup[]} */ -export const ADMIN_NAV_GROUPS = [ - { - id: 'overview', - label: 'Übersicht', - items: [{ to: '/admin', label: 'Start', end: true }], - }, +/** @type {AdminGroup[]} */ +export const ADMIN_GROUPS = [ { id: 'users', label: 'Benutzerverwaltung', - items: [{ to: '/admin/users', label: 'Profile & Rollen' }], + description: 'Profile anlegen, Rollen setzen und Recovery-E-Mails pflegen.', + items: [ + { + to: '/admin/users', + label: 'Profile & Rollen', + description: 'Neue Profile, Admin-Rolle, PIN und E-Mail pro Nutzer.', + }, + ], }, { id: 'features', label: 'Features', + description: 'Feature-Registry, Kontingente und individuelle Overrides.', items: [ - { to: '/admin/features', label: 'Feature-Registry' }, - { to: '/admin/tier-limits', label: 'Tier-Limits' }, - { to: '/admin/user-restrictions', label: 'User-Overrides' }, + { + to: '/admin/features', + label: 'Feature-Registry', + description: 'Features definieren und Kategorien zuordnen.', + }, + { + to: '/admin/tier-limits', + label: 'Tier-Limits', + description: 'Limit-Matrix pro Tier bearbeiten.', + }, + { + to: '/admin/user-restrictions', + label: 'User-Overrides', + description: 'Individuelle Feature-Limits setzen.', + }, ], }, { id: 'subscription', label: 'Subscription', + description: 'Tiers und Gutscheine für das Freemium-System.', items: [ - { to: '/admin/tiers', label: 'Tiers' }, - { to: '/admin/coupons', label: 'Coupons' }, + { to: '/admin/tiers', label: 'Tiers', description: 'Abo-Stufen und Zuordnung.' }, + { to: '/admin/coupons', label: 'Coupons', description: 'Gutscheincodes verwalten.' }, ], }, { id: 'training', label: 'Trainingstypen', + description: 'Trainingstypen, Mappings und Trainings-Profile.', items: [ - { to: '/admin/training-types', label: 'Trainingstypen' }, - { to: '/admin/activity-mappings', label: 'Activity-Mappings' }, - { to: '/admin/training-profiles', label: 'Trainings-Profile' }, + { + to: '/admin/training-types', + label: 'Trainingstypen', + description: 'Kategorien und Typen verwalten.', + }, + { + to: '/admin/activity-mappings', + label: 'Activity-Mappings', + description: 'Lernendes Zuordnungssystem (Sprache / Apple Health).', + }, + { + to: '/admin/training-profiles', + label: 'Trainings-Profile', + description: 'Training-Type-Profile (#15).', + }, ], }, { id: 'goals', label: 'Ziele & Fokus', + description: 'Ziel-Typen und Focus Areas.', items: [ - { to: '/admin/goal-types', label: 'Ziel-Typen' }, - { to: '/admin/focus-areas', label: 'Focus Areas' }, + { + to: '/admin/goal-types', + label: 'Ziel-Typen', + description: 'Custom Goal Types mit oder ohne Datenquelle.', + }, + { + to: '/admin/focus-areas', + label: 'Focus Areas', + description: 'Dynamische Fokusbereiche und Kategorien.', + }, + ], + }, + { + id: 'prompts', + label: 'KI-Prompts', + description: 'Pipeline- und Basis-Prompts für die KI-Analyse.', + items: [ + { + to: '/admin/prompts', + label: 'KI-Prompts verwalten', + description: 'Prompts bearbeiten, Stages, Test & Export.', + }, ], }, { id: 'system', label: 'Basiseinstellungen', + description: 'System-E-Mail und Platzhalter-Metadaten.', items: [ - { to: '/admin/prompts', label: 'KI-Prompts' }, - { to: '/admin/system', label: 'SMTP & Metadaten-Export' }, + { + to: '/admin/system', + label: 'SMTP & Metadaten-Export', + description: 'SMTP-Status, Test-Mail und Placeholder-Katalog (JSON/ZIP).', + }, ], }, ] + +export function adminGroupHubPath(groupId) { + return `/admin/g/${groupId}` +} + +/** + * Shell-Navigation: Übersicht + eine Zeile/Spalte pro Gruppe (ohne Einzelseiten). + * @typedef {{ id: string, label: string, to: string, end?: boolean }} AdminShellNavEntry + * @returns {AdminShellNavEntry[]} + */ +export function getAdminShellNavEntries() { + return [ + { id: 'overview', label: 'Übersicht', to: '/admin', end: true }, + ...ADMIN_GROUPS.map((g) => ({ + id: g.id, + label: g.label, + to: adminGroupHubPath(g.id), + end: false, + })), + ] +} + +/** Aktiver Shell-Eintrag inkl. Leaf-Routen der Gruppe (z. B. /admin/features → Gruppe „Features“). */ +export function adminShellEntryIsActive(pathname, entry) { + if (entry.id === 'overview') { + return pathname === '/admin' + } + const group = ADMIN_GROUPS.find((x) => x.id === entry.id) + if (!group) return false + if (pathname === adminGroupHubPath(group.id)) return true + return group.items.some((it) => pathname === it.to) +} + +/** Anzahl Unterseiten für Chip-Badge (wie KI-Analyse). */ +export function adminShellEntryItemCount(entry) { + if (entry.id === 'overview') return 0 + const g = ADMIN_GROUPS.find((x) => x.id === entry.id) + return g ? g.items.length : 0 +} diff --git a/frontend/src/layouts/AdminShell.jsx b/frontend/src/layouts/AdminShell.jsx index 0570d17..dc800c6 100644 --- a/frontend/src/layouts/AdminShell.jsx +++ b/frontend/src/layouts/AdminShell.jsx @@ -1,35 +1,46 @@ -import { Outlet, NavLink } from 'react-router-dom' -import { ADMIN_NAV_GROUPS } from '../config/adminNav' +import { Outlet, NavLink, useLocation } from 'react-router-dom' +import { + getAdminShellNavEntries, + adminShellEntryIsActive, + adminShellEntryItemCount, +} from '../config/adminNav' +/** + * Wie KI-Analyse: nur Gruppen-Chips (mobil) bzw. Seitenleiste (desktop); + * konkrete Admin-Seiten über Hub unter /admin/g/:groupId. + */ export default function AdminShell() { + const loc = useLocation() + const entries = getAdminShellNavEntries() + return (
-
- -
+
+
+ +
+
diff --git a/frontend/src/pages/AdminGroupHubPage.jsx b/frontend/src/pages/AdminGroupHubPage.jsx new file mode 100644 index 0000000..b0f4a55 --- /dev/null +++ b/frontend/src/pages/AdminGroupHubPage.jsx @@ -0,0 +1,57 @@ +import { useParams, Navigate, Link } from 'react-router-dom' +import { ADMIN_GROUPS } from '../config/adminNav' + +export default function AdminGroupHubPage() { + const { groupId } = useParams() + const group = ADMIN_GROUPS.find((g) => g.id === groupId) + + if (!group) { + return + } + + return ( +
+

+ {group.label} +

+

+ {group.description} +

+

+ Bereich wählen · {group.items.length}{' '} + {group.items.length === 1 ? 'Seite' : 'Seiten'} +

+
+ {group.items.map((item) => ( + +
+ {item.label} +
+ {item.description && ( +
+ {item.description} +
+ )} + + ))} +
+
+ + ← Zur Übersicht + +
+
+ ) +} diff --git a/frontend/src/pages/AdminHomePage.jsx b/frontend/src/pages/AdminHomePage.jsx index 8e5a8e8..8ea85d4 100644 --- a/frontend/src/pages/AdminHomePage.jsx +++ b/frontend/src/pages/AdminHomePage.jsx @@ -1,38 +1,39 @@ import { Link } from 'react-router-dom' import { Shield } from 'lucide-react' -import { ADMIN_NAV_GROUPS } from '../config/adminNav' - -const LINK_GROUPS = ADMIN_NAV_GROUPS.filter((g) => g.id !== 'overview') +import { ADMIN_GROUPS, adminGroupHubPath } from '../config/adminNav' export default function AdminHomePage() { return (
-
- -

Adminbereich

+
+ +

Adminbereich

-

- Wähle links (Desktop) oder oben (Mobil) einen Bereich – oder springe direkt zu einer Gruppe: +

+ Wähle links oder oben eine Gruppe. Die einzelnen Verwaltungsseiten erreichst du auf der + folgenden Gruppenseite – wie bei der KI-Analyse (Kategorie → Detail).

-
- {LINK_GROUPS.map((group) => ( -
-
- {group.label} +
+ {ADMIN_GROUPS.map((group) => ( + +
{group.label}
+
+ {group.description}
-
- {group.items.map((item) => ( - - {item.label} - - ))} +
+ {group.items.length} {group.items.length === 1 ? 'Bereich' : 'Bereiche'}
-
+ ))}
diff --git a/frontend/src/pages/Analysis.jsx b/frontend/src/pages/Analysis.jsx index 487a98e..1cc6ae1 100644 --- a/frontend/src/pages/Analysis.jsx +++ b/frontend/src/pages/Analysis.jsx @@ -632,7 +632,7 @@ export default function Analysis() {

Keine aktiven Pipeline-Prompts verfügbar.

- Erstelle Pipeline-Prompts im Admin-Bereich (Einstellungen → Admin → KI-Prompts). + Erstelle Pipeline-Prompts im Admin-Bereich unter Admin → KI-Prompts.

)}