Files
incidentops/web/app/dashboard/page.tsx

138 lines
4.3 KiB
TypeScript

'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>
);
}