EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
Dashboard.jsx
Go to the documentation of this file.
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';
7
8function Dashboard() {
9 const token = localStorage.getItem('token');
10 let tenantId = '';
11 if (token) {
12 try {
13 const payload = token.split('.')[1];
14 const decoded = JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/')));
15 tenantId = decoded.tenant_id || decoded.tenantId || '';
16 } catch {}
17 }
18 const [metrics, setMetrics] = useState({
19 activeTickets: 0,
20 criticalAlerts: 0,
21 onlineAgents: 0,
22 ticketsClosedThisMonth: 0,
23 invoicedThisMonth: 0,
24 paymentsThisMonth: 0,
25 recentIncidents: []
26 });
27 const [loading, setLoading] = useState(false);
28 const [error, setError] = useState('');
29 const [showDownload, setShowDownload] = useState(false);
30
31 // token already declared above
32
33 function authHeaders() {
34 return { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' };
35 }
36
37 async function fetchData(fetchTenantId = tenantId) {
38 setLoading(true);
39 setError('');
40 try {
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);
46
47 if (statsRes.ok) {
48 const stats = await statsRes.json();
49 setMetrics({
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 || []
57 });
58 } else {
59 const errorText = await statsRes.text();
60 console.error('Failed to fetch dashboard stats', statsRes.status, errorText);
61 setMetrics({
62 activeTickets: 0,
63 criticalAlerts: 0,
64 onlineAgents: 0,
65 ticketsClosedThisMonth: 0,
66 invoicedThisMonth: 0,
67 paymentsThisMonth: 0,
68 recentIncidents: []
69 });
70 }
71 } catch (err) {
72 console.error('Error in fetchData:', err);
73 setError(err.message || 'Failed to load metrics');
74 } finally {
75 setLoading(false);
76 }
77 }
78
79 // Reload dashboard data when tenantId changes
80 useEffect(() => {
81 fetchData(tenantId);
82 }, [tenantId]);
83
84 useEffect(() => {
85 // Clear metrics immediately to avoid showing stale data
86 setMetrics({
87 activeTickets: 0,
88 criticalAlerts: 0,
89 onlineAgents: 0,
90 ticketsClosedThisMonth: 0,
91 invoicedThisMonth: 0,
92 paymentsThisMonth: 0,
93 recentIncidents: []
94 });
95 fetchData(tenantId);
96
97 // Refresh dashboard every 30 seconds
98 const interval = setInterval(() => {
99 fetchData(tenantId);
100 }, 30000);
101
102 // Refresh when page becomes visible (user returns to tab)
103 const handleVisibilityChange = () => {
104 if (!document.hidden) {
105 fetchData(tenantId);
106 }
107 };
108 document.addEventListener('visibilitychange', handleVisibilityChange);
109
110 return () => {
111 clearInterval(interval);
112 document.removeEventListener('visibilitychange', handleVisibilityChange);
113 };
114 }, [tenantId]);
115
116 function handleLogout() {
117 localStorage.removeItem('token');
118 window.location.href = '/';
119 }
120
121 async function handleCreate(e) {
122 e.preventDefault();
123 setError('');
124
125 try {
126 const res = await apiFetch('/tickets', {
127 method: 'POST',
128 headers: authHeaders(),
129 body: JSON.stringify(form),
130 });
131
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' });
136 } catch (err) {
137 console.error(err);
138 setError(err.message || 'Create error');
139 }
140 }
141
142 return (
143 <MainLayout>
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}}>
148 <button
149 onClick={() => fetchData()}
150 className="btn"
151 title="Refresh dashboard"
152 disabled={loading}
153 >
154 <span className="material-symbols-outlined">refresh</span>
155 {loading ? 'Refreshing...' : 'Refresh'}
156 </button>
157 <button
158 className="btn primary"
159 onClick={() => setShowDownload(true)}
160 title="Download Agent Installer"
161 style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}
162 >
163 <span className="material-symbols-outlined" style={{ fontSize: '18px' }}>
164 download
165 </span>
166 Download Agent
167 </button>
168
169 </div>
170 </div>
171
172 {error && <div className="error-message">{error}</div>}
173
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')}
182 </div>
183 ))
184 ) : '—'}
185 </div>
186 <div className="metric-subtitle">Agent Customer</div>
187 </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>
192 </div>
193
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}
198 </div>
199 <div className="metric-subtitle">Unread system alerts</div>
200 </div>
201
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}
206 </div>
207 <div className="metric-subtitle">Active monitoring agents</div>
208 </div>
209
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}
214 </div>
215 <div className="metric-subtitle">Resolved this month</div>
216 </div>
217
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 })}
222 </div>
223 <div className="metric-subtitle">Total invoiced in {new Date().toLocaleString('en-US', { month: 'long' })}</div>
224 </div>
225
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 })}
230 </div>
231 <div className="metric-subtitle">Received in {new Date().toLocaleString('en-US', { month: 'long' })}</div>
232 </div>
233
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'}
242 </span>
243 <div className="alert-content">
244 <div className="alert-message">{incident.message}</div>
245 <div className="alert-time">{incident.time}</div>
246 </div>
247 </div>
248 ))
249 ) : (
250 <div className="no-incidents">No recent incidents</div>
251 )}
252 </div>
253 </div>
254 </div>
255 {showDownload && (
256 <DownloadAgentModal
257 isOpen={showDownload}
258 onClose={() => setShowDownload(false)}
259 tenantId={tenantId}
260 customerId={null}
261 />
262 )}
263
264 </div>
265 </MainLayout>
266 );
267}
268
269
270
271export default Dashboard;