138 lines
4.3 KiB
TypeScript
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>
|
|
);
|
|
}
|