Spaces:
Sleeping
Sleeping
File size: 7,612 Bytes
cd99321 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | import React from 'react'
import { adminApi } from '../api/client'
import DevNotificationsPanel from '../components/Admin/DevNotificationsPanel'
import DefaultUserSettingsTab from '../components/Admin/DefaultUserSettingsTab'
import { useTranslation } from '../i18n'
import PageShell from '../components/Layout/PageShell'
import CategoryManager from '../components/Admin/CategoryManager'
import BackupPanel from '../components/Admin/BackupPanel'
import GitHubPanel from '../components/Admin/GitHubPanel'
import AddonManager from '../components/Admin/AddonManager'
import PackingTemplateManager from '../components/Admin/PackingTemplateManager'
import AuditLogPanel from '../components/Admin/AuditLogPanel'
import AdminMcpTokensPanel from '../components/Admin/AdminMcpTokensPanel'
import { Users, Map, Briefcase, Shield, FileText, SlidersHorizontal, UserCog, Puzzle, Settings as SettingsIcon, Bell, Database, ScrollText, KeyRound, GitBranch, Bug } from 'lucide-react'
import PageSidebar, { type PageSidebarTab } from '../components/Layout/PageSidebar'
import { useAdmin } from './admin/useAdmin'
import AdminUpdateBanner from './admin/AdminUpdateBanner'
import AdminStatCard from './admin/AdminStatCard'
import AdminUsersTab from './admin/AdminUsersTab'
import AdminSettingsTab from './admin/AdminSettingsTab'
import AdminNotificationsTab from './admin/AdminNotificationsTab'
import AdminUserModals from './admin/AdminUserModals'
export default function AdminPage(): React.ReactElement {
const { t, locale } = useTranslation()
// Page = wiring container: all admin data slices + handlers live in the hook,
// each tab/section renders from a dedicated sub-component.
const admin = useAdmin()
const {
demoMode, mcpEnabled, devMode, toast,
activeTab, setActiveTab, stats,
bagTrackingEnabled, setBagTrackingEnabled,
collabFeatures, setCollabFeatures,
serverTimezone,
updateInfo, setShowUpdateModal,
} = admin
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 }] : []),
]
return (
<PageShell background="var(--bg-secondary)">
<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">
<Shield className="w-5 h-5 text-slate-700" />
</div>
<div>
<h1 className="text-2xl font-bold text-slate-900">{t('admin.title')}</h1>
<p className="text-slate-500 text-sm">{t('admin.subtitle')}</p>
</div>
</div>
{/* Update Banner */}
{updateInfo && (
<AdminUpdateBanner updateInfo={updateInfo} t={t} onHowTo={() => setShowUpdateModal(true)} />
)}
{/* Demo Baseline Button */}
{demoMode && (
<div className="mb-6 p-4 bg-amber-50 border border-amber-200 rounded-xl flex items-center justify-between">
<div>
<p className="text-sm font-semibold text-amber-900">Demo Baseline</p>
<p className="text-xs text-amber-700">Save current state as the hourly reset point. All admin trips and settings will be preserved.</p>
</div>
<button
onClick={async () => {
try {
await adminApi.saveDemoBaseline()
toast.success('Baseline saved! Resets will restore to this state.')
} catch (e) {
toast.error(e.response?.data?.error || 'Failed to save baseline')
}
}}
className="px-4 py-2 bg-amber-600 text-white rounded-lg text-sm font-semibold hover:bg-amber-700 transition-colors flex-shrink-0 ml-4"
>
Save Baseline
</button>
</div>
)}
{/* Stats */}
{stats && (
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4 mb-6">
{[
{ label: t('admin.stats.users'), value: stats.totalUsers, icon: Users },
{ label: t('admin.stats.trips'), value: stats.totalTrips, icon: Briefcase },
{ label: t('admin.stats.places'), value: stats.totalPlaces, icon: Map },
{ label: t('admin.stats.files'), value: stats.totalFiles || 0, icon: FileText },
].map(({ label, value, icon: Icon }) => (
<AdminStatCard key={label} label={label} value={value} icon={Icon} />
))}
</div>
)}
{/* 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' && (
<AdminUsersTab admin={admin} t={t} locale={locale} />
)}
{activeTab === 'config' && (
<div className="space-y-6">
<PackingTemplateManager />
<CategoryManager />
</div>
)}
{activeTab === 'addons' && (
<div className="space-y-6">
<AddonManager bagTrackingEnabled={bagTrackingEnabled} onToggleBagTracking={async () => {
const next = !bagTrackingEnabled
setBagTrackingEnabled(next)
try { await adminApi.updateBagTracking(next) } catch { setBagTrackingEnabled(!next) }
}} collabFeatures={collabFeatures} onToggleCollabFeature={async (key: string) => {
const next = { ...collabFeatures, [key]: !collabFeatures[key] }
setCollabFeatures(next)
try { await adminApi.updateCollabFeatures({ [key]: next[key] }) } catch { setCollabFeatures(collabFeatures) }
}} />
</div>
)}
{activeTab === 'settings' && (
<AdminSettingsTab admin={admin} t={t} />
)}
{activeTab === 'notifications' && (
<AdminNotificationsTab admin={admin} t={t} />
)}
{activeTab === 'backup' && <BackupPanel />}
{activeTab === 'audit' && <AuditLogPanel serverTimezone={serverTimezone} />}
{activeTab === 'mcp-tokens' && <AdminMcpTokensPanel />}
{activeTab === 'github' && <GitHubPanel isPrerelease={updateInfo?.is_prerelease ?? false} />}
{activeTab === 'defaults' && <DefaultUserSettingsTab />}
{activeTab === 'dev-notifications' && <DevNotificationsPanel />}
</PageSidebar>
</div>
<AdminUserModals admin={admin} t={t} />
</PageShell>
)
}
|