refactor(admin): group the admin sidebar tabs into sections

The admin sidebar had 11 flat tabs. PageSidebar now supports optional group headings (backward-compatible; the Settings sidebar stays flat), and the admin tabs are grouped into Users, Configuration, Integrations and Maintenance. Group labels added across all locales.
This commit is contained in:
Maurice
2026-06-29 13:27:05 +02:00
committed by Maurice
parent 4d131db9af
commit 1cc69fc22a
23 changed files with 136 additions and 34 deletions
+37 -23
View File
@@ -5,6 +5,9 @@ export interface PageSidebarTab {
id: string
label: string
icon: LucideIcon
/** Optional group heading shown above the first tab of each group. Tabs that
* share a group must be contiguous in the array. */
group?: string
}
interface PageSidebarProps {
@@ -160,29 +163,40 @@ function SidebarInner({
</div>
)}
<nav className="flex flex-col gap-1 flex-1">
{tabs.map((tab) => {
const Icon = tab.icon
const active = tab.id === activeTab
return (
<button
key={tab.id}
onClick={() => onTabChange(tab.id)}
className={`flex items-center gap-2.5 px-3 py-2 rounded-lg text-sm text-left transition-colors ${active ? 'text-content font-semibold' : 'text-content-secondary font-medium'}`}
style={{
background: active ? 'var(--bg-hover)' : 'transparent',
}}
onMouseEnter={(e) => {
if (!active) e.currentTarget.style.background = 'var(--bg-hover)'
}}
onMouseLeave={(e) => {
if (!active) e.currentTarget.style.background = 'transparent'
}}
>
<Icon size={16} className="shrink-0" />
<span className="truncate">{tab.label}</span>
</button>
)
})}
{(() => {
let lastGroup: string | undefined
return tabs.map((tab) => {
const Icon = tab.icon
const active = tab.id === activeTab
const showHeader = !!tab.group && tab.group !== lastGroup
lastGroup = tab.group
return (
<React.Fragment key={tab.id}>
{showHeader && (
<div className="text-[10px] font-bold tracking-widest uppercase text-content-faint px-3 mt-3 mb-0.5 first:mt-0">
{tab.group}
</div>
)}
<button
onClick={() => onTabChange(tab.id)}
className={`flex items-center gap-2.5 px-3 py-2 rounded-lg text-sm text-left transition-colors ${active ? 'text-content font-semibold' : 'text-content-secondary font-medium'}`}
style={{
background: active ? 'var(--bg-hover)' : 'transparent',
}}
onMouseEnter={(e) => {
if (!active) e.currentTarget.style.background = 'var(--bg-hover)'
}}
onMouseLeave={(e) => {
if (!active) e.currentTarget.style.background = 'transparent'
}}
>
<Icon size={16} className="shrink-0" />
<span className="truncate">{tab.label}</span>
</button>
</React.Fragment>
)
})
})()}
</nav>
{footer && (
<div
+15 -11
View File
@@ -35,18 +35,22 @@ export default function AdminPage(): React.ReactElement {
updateInfo, setShowUpdateModal,
} = admin
const gUsers = t('admin.group.users')
const gConfig = t('admin.group.config')
const gIntegration = t('admin.group.integration')
const gMaintenance = t('admin.group.maintenance')
const TABS: PageSidebarTab[] = [
{ id: 'users', label: t('admin.tabs.users'), icon: Users },
{ id: 'config', label: t('admin.tabs.config'), icon: SlidersHorizontal },
{ id: 'defaults', label: t('admin.tabs.defaults'), icon: UserCog },
{ id: 'addons', label: t('admin.tabs.addons'), icon: Puzzle },
{ id: 'settings', label: t('admin.tabs.settings'), icon: SettingsIcon },
{ id: 'notifications', label: t('admin.tabs.notifications'), icon: Bell },
{ id: 'backup', label: t('admin.tabs.backup'), icon: Database },
{ id: 'audit', label: t('admin.tabs.audit'), icon: ScrollText },
...(mcpEnabled ? [{ id: 'mcp-tokens', label: t('admin.tabs.mcpTokens'), icon: KeyRound }] : []),
{ id: 'github', label: t('admin.tabs.github'), icon: GitBranch },
...(devMode ? [{ id: 'dev-notifications', label: 'Dev: Notifications', icon: Bug }] : []),
{ id: 'users', label: t('admin.tabs.users'), icon: Users, group: gUsers },
{ id: 'defaults', label: t('admin.tabs.defaults'), icon: UserCog, group: gUsers },
{ id: 'config', label: t('admin.tabs.config'), icon: SlidersHorizontal, group: gConfig },
{ id: 'settings', label: t('admin.tabs.settings'), icon: SettingsIcon, group: gConfig },
{ id: 'addons', label: t('admin.tabs.addons'), icon: Puzzle, group: gConfig },
{ id: 'notifications', label: t('admin.tabs.notifications'), icon: Bell, group: gIntegration },
...(mcpEnabled ? [{ id: 'mcp-tokens', label: t('admin.tabs.mcpTokens'), icon: KeyRound, group: gIntegration }] : []),
{ id: 'github', label: t('admin.tabs.github'), icon: GitBranch, group: gIntegration },
{ id: 'backup', label: t('admin.tabs.backup'), icon: Database, group: gMaintenance },
{ id: 'audit', label: t('admin.tabs.audit'), icon: ScrollText, group: gMaintenance },
...(devMode ? [{ id: 'dev-notifications', label: 'Dev: Notifications', icon: Bug, group: gMaintenance }] : []),
]
return (
+4
View File
@@ -343,5 +343,9 @@ const admin: TranslationStrings = {
'admin.defaultSettings.mapboxStylePlaceholder': 'اختر نمطًا…',
'admin.defaultSettings.mapbox3d': 'المباني والتضاريس ثلاثية الأبعاد',
'admin.defaultSettings.mapboxQuality': 'وضع الجودة العالية',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
'admin.group.maintenance': 'Maintenance',
};
export default admin;
+4
View File
@@ -350,5 +350,9 @@ const admin: TranslationStrings = {
'admin.defaultSettings.mapboxStylePlaceholder': 'Escolha um estilo…',
'admin.defaultSettings.mapbox3d': 'Edifícios & relevo em 3D',
'admin.defaultSettings.mapboxQuality': 'Modo de alta qualidade',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
'admin.group.maintenance': 'Maintenance',
};
export default admin;
+4
View File
@@ -348,5 +348,9 @@ const admin: TranslationStrings = {
'admin.defaultSettings.mapboxStylePlaceholder': 'Vyberte styl…',
'admin.defaultSettings.mapbox3d': '3D budovy & terén',
'admin.defaultSettings.mapboxQuality': 'Režim vysoké kvality',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
'admin.group.maintenance': 'Maintenance',
};
export default admin;
+4
View File
@@ -353,5 +353,9 @@ const admin: TranslationStrings = {
'admin.defaultSettings.mapboxStylePlaceholder': 'Stil auswählen…',
'admin.defaultSettings.mapbox3d': '3D-Gebäude & Gelände',
'admin.defaultSettings.mapboxQuality': 'Hochqualitätsmodus',
'admin.group.users': 'Benutzer',
'admin.group.config': 'Konfiguration',
'admin.group.integration': 'Integrationen',
'admin.group.maintenance': 'Wartung',
};
export default admin;
+4
View File
@@ -348,5 +348,9 @@ const admin: TranslationStrings = {
"Remove all of this user's passkeys (e.g. on a lost device). They can still sign in with their password.",
'admin.passkey.resetConfirm': 'Remove all passkeys for {name}?',
'admin.passkey.resetDone': 'Removed {count} passkey(s)',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
'admin.group.maintenance': 'Maintenance',
};
export default admin;
+4
View File
@@ -359,5 +359,9 @@ const admin: TranslationStrings = {
'admin.defaultSettings.mapboxStylePlaceholder': 'Elige un estilo…',
'admin.defaultSettings.mapbox3d': 'Edificios y terreno en 3D',
'admin.defaultSettings.mapboxQuality': 'Modo de alta calidad',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
'admin.group.maintenance': 'Maintenance',
};
export default admin;
+4
View File
@@ -356,5 +356,9 @@ const admin: TranslationStrings = {
'admin.defaultSettings.mapboxStylePlaceholder': 'Choisissez un style…',
'admin.defaultSettings.mapbox3d': 'Bâtiments & terrain en 3D',
'admin.defaultSettings.mapboxQuality': 'Mode haute qualité',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
'admin.group.maintenance': 'Maintenance',
};
export default admin;
+4
View File
@@ -363,5 +363,9 @@ const admin: TranslationStrings = {
'admin.defaultSettings.mapboxStylePlaceholder': 'Επιλέξτε ένα στυλ…',
'admin.defaultSettings.mapbox3d': 'Κτίρια & ανάγλυφο 3D',
'admin.defaultSettings.mapboxQuality': 'Λειτουργία υψηλής ποιότητας',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
'admin.group.maintenance': 'Maintenance',
};
export default admin;
+4
View File
@@ -355,5 +355,9 @@ const admin: TranslationStrings = {
'admin.defaultSettings.mapboxStylePlaceholder': 'Válassz stílust…',
'admin.defaultSettings.mapbox3d': '3D épületek & domborzat',
'admin.defaultSettings.mapboxQuality': 'Kiváló minőségű mód',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
'admin.group.maintenance': 'Maintenance',
};
export default admin;
+4
View File
@@ -351,5 +351,9 @@ const admin: TranslationStrings = {
'admin.defaultSettings.mapboxStylePlaceholder': 'Pilih gaya…',
'admin.defaultSettings.mapbox3d': 'Bangunan & medan 3D',
'admin.defaultSettings.mapboxQuality': 'Mode kualitas tinggi',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
'admin.group.maintenance': 'Maintenance',
};
export default admin;
+4
View File
@@ -354,5 +354,9 @@ const admin: TranslationStrings = {
'admin.defaultSettings.mapboxStylePlaceholder': 'Scegli uno stile…',
'admin.defaultSettings.mapbox3d': 'Edifici & terreno in 3D',
'admin.defaultSettings.mapboxQuality': 'Modalità alta qualità',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
'admin.group.maintenance': 'Maintenance',
};
export default admin;
+4
View File
@@ -339,5 +339,9 @@ const admin: TranslationStrings = {
'admin.defaultSettings.mapboxStylePlaceholder': 'スタイルを選択…',
'admin.defaultSettings.mapbox3d': '3D の建物と地形',
'admin.defaultSettings.mapboxQuality': '高品質モード',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
'admin.group.maintenance': 'Maintenance',
};
export default admin;
+4
View File
@@ -342,5 +342,9 @@ const admin: TranslationStrings = {
'admin.defaultSettings.mapboxStylePlaceholder': '스타일을 선택하세요…',
'admin.defaultSettings.mapbox3d': '3D 건물 & 지형',
'admin.defaultSettings.mapboxQuality': '고품질 모드',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
'admin.group.maintenance': 'Maintenance',
};
export default admin;
+4
View File
@@ -354,5 +354,9 @@ const admin: TranslationStrings = {
'admin.defaultSettings.mapboxStylePlaceholder': 'Kies een stijl…',
'admin.defaultSettings.mapbox3d': '3D-gebouwen & terrein',
'admin.defaultSettings.mapboxQuality': 'Hogekwaliteitsmodus',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
'admin.group.maintenance': 'Maintenance',
};
export default admin;
+4
View File
@@ -358,5 +358,9 @@ const admin: TranslationStrings = {
'admin.defaultSettings.mapboxStylePlaceholder': 'Wybierz styl…',
'admin.defaultSettings.mapbox3d': 'Budynki i teren 3D',
'admin.defaultSettings.mapboxQuality': 'Tryb wysokiej jakości',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
'admin.group.maintenance': 'Maintenance',
};
export default admin;
+4
View File
@@ -353,5 +353,9 @@ const admin: TranslationStrings = {
'admin.defaultSettings.mapboxStylePlaceholder': 'Выберите стиль…',
'admin.defaultSettings.mapbox3d': '3D-здания и рельеф',
'admin.defaultSettings.mapboxQuality': 'Режим высокого качества',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
'admin.group.maintenance': 'Maintenance',
};
export default admin;
+4
View File
@@ -348,5 +348,9 @@ const admin: TranslationStrings = {
"Ta bort alla den här användarens inloggningsnycklar (t.ex. om enheten har försvunnit). Användaren kan fortfarande logga in med sitt lösenord.",
'admin.passkey.resetConfirm': 'Ta bort alla åtkomstnycklar för {name}?',
'admin.passkey.resetDone': 'Tog bort {count} inloggningsnycklar',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
'admin.group.maintenance': 'Maintenance',
};
export default admin;
+4
View File
@@ -357,5 +357,9 @@ const admin: TranslationStrings = {
'admin.defaultSettings.mapboxStylePlaceholder': 'Bir stil seçin…',
'admin.defaultSettings.mapbox3d': '3D binalar & arazi',
'admin.defaultSettings.mapboxQuality': 'Yüksek kalite modu',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
'admin.group.maintenance': 'Maintenance',
};
export default admin;
+4
View File
@@ -353,5 +353,9 @@ const admin: TranslationStrings = {
'admin.defaultSettings.mapboxStylePlaceholder': 'Виберіть стиль…',
'admin.defaultSettings.mapbox3d': '3D-будівлі та рельєф',
'admin.defaultSettings.mapboxQuality': 'Режим високої якості',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
'admin.group.maintenance': 'Maintenance',
};
export default admin;
+4
View File
@@ -335,5 +335,9 @@ const admin: TranslationStrings = {
'admin.defaultSettings.mapboxStylePlaceholder': '選擇樣式…',
'admin.defaultSettings.mapbox3d': '3D 建築物與地形',
'admin.defaultSettings.mapboxQuality': '高品質模式',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
'admin.group.maintenance': 'Maintenance',
};
export default admin;
+4
View File
@@ -333,5 +333,9 @@ const admin: TranslationStrings = {
'admin.defaultSettings.mapboxStylePlaceholder': '选择一种样式…',
'admin.defaultSettings.mapbox3d': '3D 建筑与地形',
'admin.defaultSettings.mapboxQuality': '高质量模式',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
'admin.group.maintenance': 'Maintenance',
};
export default admin;