mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 22:31:46 +00:00
feat(ui): introduce shared PageSidebar for Settings and Admin
Replaces the inline tab bar on SettingsPage and AdminPage with a responsive sidebar layout (left nav on desktop, hamburger drawer on mobile). Each tab gets a lucide-react icon for quick scanning. Both pages drop max-w-6xl so the panel fills the viewport.
This commit is contained in:
@@ -20,8 +20,9 @@ import PackingTemplateManager from '../components/Admin/PackingTemplateManager'
|
||||
import AuditLogPanel from '../components/Admin/AuditLogPanel'
|
||||
import AdminMcpTokensPanel from '../components/Admin/AdminMcpTokensPanel'
|
||||
import PermissionsPanel from '../components/Admin/PermissionsPanel'
|
||||
import { Users, Map, Briefcase, Shield, Trash2, Edit2, FileText, Eye, EyeOff, Save, CheckCircle, XCircle, Loader2, UserPlus, ArrowUpCircle, ExternalLink, Download, Sun, Link2, Copy, Plus, RefreshCw, AlertTriangle } from 'lucide-react'
|
||||
import { Users, Map, Briefcase, Shield, Trash2, Edit2, FileText, Eye, EyeOff, Save, CheckCircle, XCircle, Loader2, UserPlus, ArrowUpCircle, ExternalLink, Download, Sun, Link2, Copy, Plus, RefreshCw, AlertTriangle, SlidersHorizontal, UserCog, Puzzle, Settings as SettingsIcon, Bell, Database, ScrollText, KeyRound, GitBranch, Bug } from 'lucide-react'
|
||||
import CustomSelect from '../components/shared/CustomSelect'
|
||||
import PageSidebar, { type PageSidebarTab } from '../components/Layout/PageSidebar'
|
||||
|
||||
interface AdminUser {
|
||||
id: number
|
||||
@@ -183,18 +184,18 @@ export default function AdminPage(): React.ReactElement {
|
||||
const hour12 = useSettingsStore(s => s.settings.time_format) === '12h'
|
||||
const mcpEnabled = useAddonStore(s => s.isEnabled('mcp'))
|
||||
const devMode = useAuthStore(s => s.devMode)
|
||||
const TABS = [
|
||||
{ id: 'users', label: t('admin.tabs.users') },
|
||||
{ id: 'config', label: t('admin.tabs.config') },
|
||||
{ id: 'defaults', label: t('admin.tabs.defaults') },
|
||||
{ id: 'addons', label: t('admin.tabs.addons') },
|
||||
{ id: 'settings', label: t('admin.tabs.settings') },
|
||||
{ id: 'notifications', label: t('admin.tabs.notifications') },
|
||||
{ id: 'backup', label: t('admin.tabs.backup') },
|
||||
{ id: 'audit', label: t('admin.tabs.audit') },
|
||||
...(mcpEnabled ? [{ id: 'mcp-tokens', label: t('admin.tabs.mcpTokens') }] : []),
|
||||
{ id: 'github', label: t('admin.tabs.github') },
|
||||
...(devMode ? [{ id: 'dev-notifications', label: 'Dev: Notifications' }] : []),
|
||||
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 }] : []),
|
||||
]
|
||||
|
||||
const [activeTab, setActiveTab] = useState<string>('users')
|
||||
@@ -500,7 +501,7 @@ export default function AdminPage(): React.ReactElement {
|
||||
<Navbar />
|
||||
|
||||
<div style={{ paddingTop: 'var(--nav-h)' }}>
|
||||
<div className="max-w-6xl mx-auto px-4 py-8">
|
||||
<div className="w-full px-4 sm:px-6 lg:px-8 py-8">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-10 h-10 bg-slate-100 rounded-xl flex items-center justify-center">
|
||||
@@ -586,24 +587,15 @@ export default function AdminPage(): React.ReactElement {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="grid grid-cols-3 sm:flex gap-1 mb-6 rounded-xl p-1" style={{ background: 'var(--bg-card)', border: '1px solid var(--border-primary)' }}>
|
||||
{TABS.map(tab => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`px-3 sm:px-4 py-2 text-xs sm:text-sm font-medium rounded-lg transition-colors ${
|
||||
activeTab === tab.id
|
||||
? 'bg-slate-900 text-white'
|
||||
: 'text-slate-600 hover:text-slate-900 hover:bg-slate-50'
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Tab content */}
|
||||
{/* Sidebar layout — nav on the left, active panel on the right */}
|
||||
<PageSidebar
|
||||
sidebarLabel={t('admin.title').toUpperCase()}
|
||||
tabs={TABS}
|
||||
activeTab={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
footer="admin · self-hosted"
|
||||
>
|
||||
{/* Tab content */}
|
||||
{activeTab === 'users' && (
|
||||
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
||||
<div className="p-5 border-b border-slate-100 flex items-center justify-between">
|
||||
@@ -1618,6 +1610,7 @@ export default function AdminPage(): React.ReactElement {
|
||||
{activeTab === 'defaults' && <DefaultUserSettingsTab />}
|
||||
|
||||
{activeTab === 'dev-notifications' && <DevNotificationsPanel />}
|
||||
</PageSidebar>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import { Settings } from 'lucide-react'
|
||||
import { Settings, Palette, Map, Bell, Plug, CloudOff, User, Info } from 'lucide-react'
|
||||
import { useTranslation } from '../i18n'
|
||||
import { authApi } from '../api/client'
|
||||
import { useAddonStore } from '../store/addonStore'
|
||||
import Navbar from '../components/Layout/Navbar'
|
||||
import PageSidebar, { type PageSidebarTab } from '../components/Layout/PageSidebar'
|
||||
import DisplaySettingsTab from '../components/Settings/DisplaySettingsTab'
|
||||
import MapSettingsTab from '../components/Settings/MapSettingsTab'
|
||||
import NotificationsTab from '../components/Settings/NotificationsTab'
|
||||
@@ -37,14 +38,18 @@ export default function SettingsPage(): React.ReactElement {
|
||||
}
|
||||
}, [searchParams])
|
||||
|
||||
const TABS = [
|
||||
{ id: 'display', label: t('settings.tabs.display') },
|
||||
{ id: 'map', label: t('settings.tabs.map') },
|
||||
{ id: 'notifications', label: t('settings.tabs.notifications') },
|
||||
...(hasIntegrations ? [{ id: 'integrations', label: t('settings.tabs.integrations') }] : []),
|
||||
{ id: 'offline', label: t('settings.tabs.offline') },
|
||||
{ id: 'account', label: t('settings.tabs.account') },
|
||||
...(appVersion ? [{ id: 'about', label: t('settings.tabs.about') }] : []),
|
||||
const tabs: PageSidebarTab[] = [
|
||||
{ id: 'display', label: t('settings.tabs.display'), icon: Palette },
|
||||
{ id: 'map', label: t('settings.tabs.map'), icon: Map },
|
||||
{ id: 'notifications', label: t('settings.tabs.notifications'), icon: Bell },
|
||||
...(hasIntegrations
|
||||
? [{ id: 'integrations', label: t('settings.tabs.integrations'), icon: Plug }]
|
||||
: []),
|
||||
{ id: 'offline', label: t('settings.tabs.offline'), icon: CloudOff },
|
||||
{ id: 'account', label: t('settings.tabs.account'), icon: User },
|
||||
...(appVersion
|
||||
? [{ id: 'about', label: t('settings.tabs.about'), icon: Info }]
|
||||
: []),
|
||||
]
|
||||
|
||||
return (
|
||||
@@ -52,7 +57,7 @@ export default function SettingsPage(): React.ReactElement {
|
||||
<Navbar />
|
||||
|
||||
<div style={{ paddingTop: 'var(--nav-h)' }}>
|
||||
<div className="max-w-6xl mx-auto px-4 py-8">
|
||||
<div className="w-full px-4 sm:px-6 lg:px-8 py-8">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-10 h-10 rounded-xl flex items-center justify-center" style={{ background: 'var(--bg-tertiary)' }}>
|
||||
@@ -64,33 +69,24 @@ export default function SettingsPage(): React.ReactElement {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tab bar */}
|
||||
<div className="grid grid-cols-3 sm:flex gap-1 mb-6 rounded-xl p-1" style={{ background: 'var(--bg-card)', border: '1px solid var(--border-primary)' }}>
|
||||
{TABS.map(tab => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`px-3 sm:px-4 py-2 text-xs sm:text-sm font-medium rounded-lg transition-colors ${
|
||||
activeTab === tab.id
|
||||
? 'bg-slate-900 text-white'
|
||||
: 'text-slate-600 hover:text-slate-900 hover:bg-slate-50'
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Tab content */}
|
||||
{activeTab === 'display' && <DisplaySettingsTab />}
|
||||
{activeTab === 'map' && <MapSettingsTab />}
|
||||
{activeTab === 'notifications' && <NotificationsTab />}
|
||||
{activeTab === 'integrations' && hasIntegrations && <IntegrationsTab />}
|
||||
{activeTab === 'offline' && <OfflineTab />}
|
||||
{activeTab === 'account' && <AccountTab />}
|
||||
{activeTab === 'about' && appVersion && <AboutTab appVersion={appVersion} />}
|
||||
{/* Sidebar layout */}
|
||||
<PageSidebar
|
||||
sidebarLabel={t('settings.title').toUpperCase()}
|
||||
tabs={tabs}
|
||||
activeTab={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
footer={appVersion ? `v${appVersion} · self-hosted` : 'self-hosted'}
|
||||
>
|
||||
{activeTab === 'display' && <DisplaySettingsTab />}
|
||||
{activeTab === 'map' && <MapSettingsTab />}
|
||||
{activeTab === 'notifications' && <NotificationsTab />}
|
||||
{activeTab === 'integrations' && hasIntegrations && <IntegrationsTab />}
|
||||
{activeTab === 'offline' && <OfflineTab />}
|
||||
{activeTab === 'account' && <AccountTab />}
|
||||
{activeTab === 'about' && appVersion && <AboutTab appVersion={appVersion} />}
|
||||
</PageSidebar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user