First build
Some checks failed
Client Admin / build-deploy (push) Failing after 8s
Client Client / build-deploy (push) Failing after 3s
Client Registration / build-deploy (push) Failing after 20s
Client Tech / build-deploy (push) Failing after 1s
Client Home / build-deploy (push) Successful in 14s
Some checks failed
Client Admin / build-deploy (push) Failing after 8s
Client Client / build-deploy (push) Failing after 3s
Client Registration / build-deploy (push) Failing after 20s
Client Tech / build-deploy (push) Failing after 1s
Client Home / build-deploy (push) Successful in 14s
This commit is contained in:
@@ -1,119 +1,170 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useAuth } from '../auth/AuthProvider';
|
||||
import { API_BASE_URL } from '../auth/authConfig';
|
||||
import ClientsPanel from './admin/ClientsPanel';
|
||||
import UsersPanel from './admin/UsersPanel';
|
||||
import React, { useRef } from 'react';
|
||||
import { useAdmin, CATEGORY_LABELS, TAB_ENDPOINTS } from '../context/AdminContext';
|
||||
import { TemplatesProvider } from '../context/TemplatesContext';
|
||||
import { ObjectiveMappingsProvider } from '../context/ObjectiveMappingsContext';
|
||||
import Sidebar from './Sidebar';
|
||||
import ClientUsersPanel from './admin/ClientUsersPanel';
|
||||
import SessionsPanel from './admin/SessionsPanel';
|
||||
import TemplatesPanel from './admin/TemplatesPanel';
|
||||
import ObjectiveMappingPanel from './admin/ObjectiveMappingPanel';
|
||||
import CampaignsPanel from './admin/CampaignsPanel';
|
||||
import IntelligencePanel from './admin/IntelligencePanel';
|
||||
import ModifiersPanel from './admin/ModifiersPanel';
|
||||
import ClientManagementPanel from './admin/ClientManagementPanel';
|
||||
import ClientActivityPanel from './admin/ClientActivityPanel';
|
||||
import ClientDocumentsPanel from './admin/ClientDocumentsPanel';
|
||||
import DocumentsPanel from './admin/DocumentsPanel';
|
||||
|
||||
export default function Dashboard() {
|
||||
const { session } = useAuth();
|
||||
const [activeTab, setActiveTab] = useState('overview');
|
||||
const [data, setData] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [refreshKey, setRefreshKey] = useState(0);
|
||||
|
||||
const fetchData = useCallback(async (endpoint) => {
|
||||
if (!session?.sessionToken) return null;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
|
||||
headers: { 'X-Session-Token': session.sessionToken }
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!result.ok) throw new Error(result.error || 'Request failed');
|
||||
return result;
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
return null;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [session]);
|
||||
|
||||
const refresh = () => setRefreshKey(k => k + 1);
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
let result = null;
|
||||
switch (activeTab) {
|
||||
case 'overview':
|
||||
result = await fetchData('/api/monitoring/health');
|
||||
break;
|
||||
case 'clients':
|
||||
result = await fetchData('/api/admin/clients');
|
||||
break;
|
||||
case 'users':
|
||||
result = await fetchData('/api/admin/users');
|
||||
break;
|
||||
case 'sessions':
|
||||
result = await fetchData('/api/admin/sessions');
|
||||
break;
|
||||
}
|
||||
setData(result);
|
||||
};
|
||||
loadData();
|
||||
}, [activeTab, fetchData, refreshKey]);
|
||||
|
||||
const tabs = [
|
||||
{ id: 'overview', label: 'Overview' },
|
||||
{ id: 'clients', label: 'Clients' },
|
||||
{ id: 'users', label: 'Users' },
|
||||
{ id: 'sessions', label: 'Sessions' },
|
||||
];
|
||||
const {
|
||||
user, userRole, isAuthenticated,
|
||||
activeCategory, activeTab, tabs, collapsed,
|
||||
setActiveCategory, setActiveTab, setCollapsed,
|
||||
data, loading, error,
|
||||
} = useAdmin();
|
||||
const tabBarRef = useRef(null);
|
||||
|
||||
return (
|
||||
<div className="dashboard">
|
||||
<div className="dashboard-header">
|
||||
<h1>Management Dashboard</h1>
|
||||
<div className="dashboard-info">
|
||||
<span className="info-item"><strong>Client:</strong> {session?.clientName}</span>
|
||||
<span className="info-item"><strong>Role:</strong> {session?.role}</span>
|
||||
<div className="dashboard-layout">
|
||||
{/* Sidebar */}
|
||||
<Sidebar
|
||||
activeCategory={activeCategory}
|
||||
onSelectCategory={setActiveCategory}
|
||||
collapsed={collapsed}
|
||||
onToggleCollapse={() => setCollapsed(c => !c)}
|
||||
/>
|
||||
|
||||
{/* Main area */}
|
||||
<div className="dashboard-main">
|
||||
{/* Header with category title + tabs */}
|
||||
<header className="dashboard-header">
|
||||
<div className="dashboard-header-top">
|
||||
<h1 className="dashboard-title">
|
||||
{CATEGORY_LABELS[activeCategory] || activeCategory}
|
||||
</h1>
|
||||
<div className="dashboard-header-right">
|
||||
<span className="dashboard-meta">
|
||||
{user?.displayName && <span>{user.displayName}</span>}
|
||||
{userRole && <span><strong>Role:</strong> {userRole}</span>}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Horizontal tabs (within the current category) */}
|
||||
{tabs.length > 1 && (
|
||||
<div className="dashboard-tabs" ref={tabBarRef}>
|
||||
{tabs.map(tab => (
|
||||
<button
|
||||
key={tab.id}
|
||||
className={`tab-btn ${activeTab === tab.id ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{/* Content area */}
|
||||
<div className="dashboard-content">
|
||||
|
||||
{/* Loading state — full spinner only on initial load */}
|
||||
{loading && !data && (
|
||||
<div className="loading-container">
|
||||
<div className="spinner"></div>
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Refreshing indicator — subtle, panels stay mounted */}
|
||||
{loading && data && (
|
||||
<div style={{ padding: '4px 12px', fontSize: '12px', color: '#888', textAlign: 'right' }}>
|
||||
Refreshing…
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error state */}
|
||||
{error && !loading && (
|
||||
<div className="error-message" style={{ margin: '0 0 16px 0' }}>
|
||||
<strong>Error:</strong> {error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Data-driven panels — stay mounted during refresh */}
|
||||
{(data || (error && activeTab === 'templates') || activeTab === 'objectives'
|
||||
|| activeTab === 'modifiers'
|
||||
|| activeTab === 'performance' || activeTab === 'insights' || activeTab === 'analysis'
|
||||
|| activeTab === 'pending' || activeTab === 'allClients'
|
||||
|| activeTab === 'clientActivity'
|
||||
|| activeTab === 'clientDocuments'
|
||||
|| activeTab === 'documents') && (
|
||||
<div className="data-panel" style={{ opacity: loading ? 0.6 : 1, transition: 'opacity 0.2s' }}>
|
||||
{activeTab === 'overview' && data && <OverviewPanel data={data} />}
|
||||
{activeTab === 'sessions' && data && <SessionsPanel />}
|
||||
{activeTab === 'clientUsers' && data && <ClientUsersPanel />}
|
||||
{activeTab === 'templates' && (
|
||||
<TemplatesProvider>
|
||||
<TemplatesPanel />
|
||||
</TemplatesProvider>
|
||||
)}
|
||||
{activeTab === 'objectives' && (
|
||||
<ObjectiveMappingsProvider>
|
||||
<ObjectiveMappingPanel />
|
||||
</ObjectiveMappingsProvider>
|
||||
)}
|
||||
{activeTab === 'modifiers' && <ModifiersPanel />}
|
||||
{activeTab === 'campaigns' && data && <CampaignsPanel />}
|
||||
{(activeTab === 'performance' || activeTab === 'insights' || activeTab === 'analysis') && (
|
||||
<IntelligencePanel activeTab={activeTab} />
|
||||
)}
|
||||
{(activeTab === 'pending' || activeTab === 'allClients') && (
|
||||
<ClientManagementPanel activeTab={activeTab} />
|
||||
)}
|
||||
{activeTab === 'clientActivity' && <ClientActivityPanel />}
|
||||
{activeTab === 'clientDocuments' && <ClientDocumentsPanel />}
|
||||
{activeTab === 'documents' && <DocumentsPanel />}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Placeholder for tabs without API endpoints */}
|
||||
{!loading && !error && !data && !TAB_ENDPOINTS[activeTab]
|
||||
&& activeTab !== 'pending' && activeTab !== 'allClients'
|
||||
&& activeTab !== 'clientActivity'
|
||||
&& activeTab !== 'clientDocuments'
|
||||
&& activeTab !== 'performance' && activeTab !== 'insights' && activeTab !== 'analysis'
|
||||
&& activeTab !== 'documents' && (
|
||||
<div className="placeholder-panel">
|
||||
<div className="placeholder-icon">🚧</div>
|
||||
<h3>{activeTab.charAt(0).toUpperCase() + activeTab.slice(1)}</h3>
|
||||
<p>This section is under development.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Fallback */}
|
||||
{!loading && !error && !data && activeTab !== 'templates'
|
||||
&& activeTab !== 'modifiers'
|
||||
&& activeTab !== 'pending' && activeTab !== 'allClients'
|
||||
&& activeTab !== 'clientActivity'
|
||||
&& activeTab !== 'clientDocuments'
|
||||
&& activeTab !== 'performance' && activeTab !== 'insights' && activeTab !== 'analysis'
|
||||
&& activeTab !== 'documents'
|
||||
&& TAB_ENDPOINTS[activeTab] && (
|
||||
<div style={{ padding: '40px', textAlign: 'center', color: '#888' }}>
|
||||
<p>No data loaded. Check the browser console for logs.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav className="dashboard-tabs">
|
||||
{tabs.map(tab => (
|
||||
<button
|
||||
key={tab.id}
|
||||
className={`tab-btn ${activeTab === tab.id ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div className="dashboard-content">
|
||||
{loading && (
|
||||
<div className="loading-container">
|
||||
<div className="spinner"></div>
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <div className="error-message">{error}</div>}
|
||||
|
||||
{!loading && !error && data && (
|
||||
<div className="data-panel">
|
||||
{activeTab === 'overview' && <OverviewPanel data={data} />}
|
||||
{activeTab === 'clients' && <ClientsPanel data={data} sessionToken={session?.sessionToken} onRefresh={refresh} />}
|
||||
{activeTab === 'users' && <UsersPanel data={data} sessionToken={session?.sessionToken} onRefresh={refresh} />}
|
||||
{activeTab === 'sessions' && <SessionsPanel data={data} sessionToken={session?.sessionToken} onRefresh={refresh} />}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// ─── Overview Panel (lightweight, stays local) ───────────────
|
||||
function OverviewPanel({ data }) {
|
||||
return (
|
||||
<div className="overview-panel">
|
||||
<h2>System Overview</h2>
|
||||
<div className="stats-grid">
|
||||
<StatCard label="Active Clients" value={data.activeClients} />
|
||||
<StatCard label="Active Users" value={data.activeUsers} />
|
||||
@@ -128,7 +179,7 @@ function OverviewPanel({ data }) {
|
||||
function StatCard({ label, value }) {
|
||||
return (
|
||||
<div className="stat-card">
|
||||
<div className="stat-value">{value ?? '-'}</div>
|
||||
<div className="stat-value">{value ?? '—'}</div>
|
||||
<div className="stat-label">{label}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user