feat(web): add dashboard page
This commit is contained in:
137
web/app/dashboard/page.tsx
Normal file
137
web/app/dashboard/page.tsx
Normal file
@@ -0,0 +1,137 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { api } from '@/lib/api';
|
||||
import { Incident, ActiveOrg } from '@/types';
|
||||
|
||||
export default function DashboardPage() {
|
||||
const router = useRouter();
|
||||
const [incidents, setIncidents] = useState<Incident[]>([]);
|
||||
const [activeOrg, setActiveOrg] = useState<ActiveOrg | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem('accessToken');
|
||||
if (!token) {
|
||||
router.push('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
api.setAccessToken(token);
|
||||
const org = localStorage.getItem('activeOrg');
|
||||
if (org) {
|
||||
setActiveOrg(JSON.parse(org));
|
||||
}
|
||||
|
||||
loadIncidents();
|
||||
}, [router]);
|
||||
|
||||
const loadIncidents = async () => {
|
||||
try {
|
||||
const response = await api.getIncidents();
|
||||
setIncidents(response.items);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to load incidents');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogout = async () => {
|
||||
const refreshToken = localStorage.getItem('refreshToken');
|
||||
if (refreshToken) {
|
||||
try {
|
||||
await api.logout(refreshToken);
|
||||
} catch {
|
||||
// Ignore logout errors
|
||||
}
|
||||
}
|
||||
localStorage.removeItem('accessToken');
|
||||
localStorage.removeItem('refreshToken');
|
||||
localStorage.removeItem('activeOrg');
|
||||
api.setAccessToken(null);
|
||||
router.push('/login');
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'triggered': return 'var(--error)';
|
||||
case 'acknowledged': return 'var(--warning)';
|
||||
case 'mitigated': return 'var(--primary)';
|
||||
case 'resolved': return 'var(--success)';
|
||||
default: return 'inherit';
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="container" style={{ textAlign: 'center', marginTop: '4rem' }}>
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<header style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem' }}>
|
||||
<div>
|
||||
<h1>IncidentOps</h1>
|
||||
{activeOrg && (
|
||||
<p style={{ color: '#666' }}>
|
||||
{activeOrg.name} ({activeOrg.role})
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<button onClick={handleLogout} style={{ background: '#666' }}>
|
||||
Logout
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
|
||||
<h2>Incidents</h2>
|
||||
<button onClick={() => router.push('/incidents/new')}>
|
||||
New Incident
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{error && <p style={{ color: 'var(--error)', marginBottom: '1rem' }}>{error}</p>}
|
||||
|
||||
{incidents.length === 0 ? (
|
||||
<p style={{ color: '#666' }}>No incidents found.</p>
|
||||
) : (
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr style={{ borderBottom: '2px solid #ccc' }}>
|
||||
<th style={{ textAlign: 'left', padding: '0.5rem' }}>Title</th>
|
||||
<th style={{ textAlign: 'left', padding: '0.5rem' }}>Service</th>
|
||||
<th style={{ textAlign: 'left', padding: '0.5rem' }}>Status</th>
|
||||
<th style={{ textAlign: 'left', padding: '0.5rem' }}>Created</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{incidents.map((incident) => (
|
||||
<tr
|
||||
key={incident.id}
|
||||
style={{ borderBottom: '1px solid #eee', cursor: 'pointer' }}
|
||||
onClick={() => router.push(`/incidents/${incident.id}`)}
|
||||
>
|
||||
<td style={{ padding: '0.5rem' }}>{incident.title}</td>
|
||||
<td style={{ padding: '0.5rem' }}>{incident.serviceName}</td>
|
||||
<td style={{ padding: '0.5rem', color: getStatusColor(incident.status) }}>
|
||||
{incident.status}
|
||||
</td>
|
||||
<td style={{ padding: '0.5rem' }}>
|
||||
{new Date(incident.createdAt).toLocaleString()}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user