1import { useEffect, useState } from 'react';
2import { apiFetch } from '../lib/api';
3import MainLayout from '../components/Layout/MainLayout';
4// import removed: TenantSelector
5import DownloadAgentModal from "../components/DownloadAgentModal";
6import './Dashboard.css';
9 const token = localStorage.getItem('token');
13 const payload = token.split('.')[1];
14 const decoded = JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/')));
15 tenantId = decoded.tenant_id || decoded.tenantId || '';
18 const [metrics, setMetrics] = useState({
22 ticketsClosedThisMonth: 0,
27 const [loading, setLoading] = useState(false);
28 const [error, setError] = useState('');
29 const [showDownload, setShowDownload] = useState(false);
31 // token already declared above
33 function authHeaders() {
34 return { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' };
37 async function fetchData(fetchTenantId = tenantId) {
41 let url = '/dashboard/stats';
42 url += `?tenant_id=${fetchTenantId}`;
43 const statsRes = await apiFetch(url);
44 console.log('[Dashboard] apiFetch called with:', url);
45 console.log('[Dashboard] apiFetch response:', statsRes);
48 const stats = await statsRes.json();
50 activeTickets: stats.activeTickets || 0,
51 criticalAlerts: stats.criticalAlerts || 0,
52 onlineAgents: stats.onlineAgents || 0,
53 ticketsClosedThisMonth: stats.ticketsClosedThisMonth || 0,
54 invoicedThisMonth: stats.invoicedThisMonth || 0,
55 paymentsThisMonth: stats.paymentsThisMonth || 0,
56 recentIncidents: stats.recentIncidents || []
59 const errorText = await statsRes.text();
60 console.error('Failed to fetch dashboard stats', statsRes.status, errorText);
65 ticketsClosedThisMonth: 0,
72 console.error('Error in fetchData:', err);
73 setError(err.message || 'Failed to load metrics');
79 // Reload dashboard data when tenantId changes
85 // Clear metrics immediately to avoid showing stale data
90 ticketsClosedThisMonth: 0,
97 // Refresh dashboard every 30 seconds
98 const interval = setInterval(() => {
102 // Refresh when page becomes visible (user returns to tab)
103 const handleVisibilityChange = () => {
104 if (!document.hidden) {
108 document.addEventListener('visibilitychange', handleVisibilityChange);
111 clearInterval(interval);
112 document.removeEventListener('visibilitychange', handleVisibilityChange);
116 function handleLogout() {
117 localStorage.removeItem('token');
118 window.location.href = '/';
121 async function handleCreate(e) {
126 const res = await apiFetch('/tickets', {
128 headers: authHeaders(),
129 body: JSON.stringify(form),
132 if (!res.ok) throw new Error('Failed to create ticket');
133 const newTicket = await res.json();
134 setTickets((t) => [newTicket, ...t]);
135 setForm({ title: '', description: '', priority: 'medium' });
138 setError(err.message || 'Create error');
144 <div className="dashboard-content">
145 <div className="page-header" style={{display:'flex',alignItems:'center',justifyContent:'space-between'}}>
146 <h2 style={{margin:0}}>System Overview</h2>
147 <div style={{display:'flex',gap:8}}>
149 onClick={() => fetchData()}
151 title="Refresh dashboard"
154 <span className="material-symbols-outlined">refresh</span>
155 {loading ? 'Refreshing...' : 'Refresh'}
158 className="btn primary"
159 onClick={() => setShowDownload(true)}
160 title="Download Agent Installer"
161 style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}
163 <span className="material-symbols-outlined" style={{ fontSize: '18px' }}>
172 {error && <div className="error-message">{error}</div>}
174 <div className="dashboard-grid">
175 <div className="metric-card">
176 <div className="metric-title">Customer</div>
177 <div className="metric-value">
178 {metrics.agents && metrics.agents.length > 0 ? (
179 metrics.agents.map(agent => (
180 <div key={agent.agent_uuid}>
181 {agent.customer_name ? agent.customer_name : (agent.customer_id ? `#${agent.customer_id}` : 'No Customer')}
186 <div className="metric-subtitle">Agent Customer</div>
188 <div className="metric-card">
189 <div className="metric-title">Active Tickets</div>
190 <div className="metric-value">{metrics.activeTickets}</div>
191 <div className="metric-subtitle">Open support requests</div>
194 <div className="metric-card">
195 <div className="metric-title">Critical Alerts</div>
196 <div className="metric-value" style={{ color: metrics.criticalAlerts > 0 ? 'var(--error, #dc3545)' : 'var(--success, #28a745)' }}>
197 {metrics.criticalAlerts}
199 <div className="metric-subtitle">Unread system alerts</div>
202 <div className="metric-card">
203 <div className="metric-title">Online Agents</div>
204 <div className="metric-value" style={{ color: 'var(--info, #17a2b8)' }}>
205 {metrics.onlineAgents}
207 <div className="metric-subtitle">Active monitoring agents</div>
210 <div className="metric-card">
211 <div className="metric-title">Tickets Closed This Month</div>
212 <div className="metric-value" style={{ color: 'var(--success, #28a745)' }}>
213 {metrics.ticketsClosedThisMonth}
215 <div className="metric-subtitle">Resolved this month</div>
218 <div className="metric-card">
219 <div className="metric-title">Invoiced This Month</div>
220 <div className="metric-value" style={{ color: 'var(--primary, #0066cc)', fontSize: '2rem' }}>
221 ${metrics.invoicedThisMonth.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
223 <div className="metric-subtitle">Total invoiced in {new Date().toLocaleString('en-US', { month: 'long' })}</div>
226 <div className="metric-card">
227 <div className="metric-title">Payments This Month</div>
228 <div className="metric-value" style={{ color: 'var(--success, #28a745)', fontSize: '2rem' }}>
229 ${metrics.paymentsThisMonth.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
231 <div className="metric-subtitle">Received in {new Date().toLocaleString('en-US', { month: 'long' })}</div>
234 <div className="metric-card alerts-card">
235 <h3>Recent Incidents</h3>
236 <div className="alert-list">
237 {metrics.recentIncidents.length > 0 ? (
238 metrics.recentIncidents.map(incident => (
239 <div key={incident.id} className={`alert-item ${incident.type}`}>
240 <span className="material-symbols-outlined alert-icon">
241 {incident.type === 'alert' ? 'error' : 'warning'}
243 <div className="alert-content">
244 <div className="alert-message">{incident.message}</div>
245 <div className="alert-time">{incident.time}</div>
250 <div className="no-incidents">No recent incidents</div>
257 isOpen={showDownload}
258 onClose={() => setShowDownload(false)}
271export default Dashboard;