188 lines
6.4 KiB
TypeScript
188 lines
6.4 KiB
TypeScript
import {
|
|
Badge,
|
|
Box,
|
|
Button,
|
|
Code,
|
|
Divider,
|
|
Drawer,
|
|
Group,
|
|
Paper,
|
|
ScrollArea,
|
|
Stack,
|
|
Table,
|
|
Text,
|
|
Title
|
|
} from '@mantine/core'
|
|
import { useDisclosure } from '@mantine/hooks'
|
|
import { useQuery } from '@tanstack/react-query'
|
|
import { Link } from '@tanstack/react-router'
|
|
import { useState } from 'react'
|
|
import { TbBug, TbExternalLink, TbHistory, TbMessageReport } from 'react-icons/tb'
|
|
|
|
export interface ErrorDataTableProps {
|
|
appId?: string
|
|
}
|
|
|
|
export function ErrorDataTable({ appId }: ErrorDataTableProps) {
|
|
const [opened, { open, close }] = useDisclosure(false)
|
|
const [selectedError, setSelectedError] = useState<any>(null)
|
|
const [showStackTrace, setShowStackTrace] = useState(false)
|
|
|
|
const { data: bugsData, isLoading } = useQuery({
|
|
queryKey: ['bugs', appId],
|
|
queryFn: () => fetch(`/api/bugs?app=${appId || 'all'}&limit=10`).then((r) => r.json()),
|
|
})
|
|
|
|
const bugs = bugsData?.data || []
|
|
|
|
const handleRowClick = (error: any) => {
|
|
setSelectedError(error)
|
|
setShowStackTrace(false)
|
|
open()
|
|
}
|
|
|
|
const getSeverityColor = (sev: string) => {
|
|
switch (sev?.toUpperCase()) {
|
|
case 'OPEN': return 'red'
|
|
case 'IN_PROGRESS': return 'orange'
|
|
case 'ON_HOLD': return 'yellow'
|
|
default: return 'gray'
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Paper withBorder radius="2xl" className="glass overflow-hidden">
|
|
<Box p="xl" style={{ borderBottom: '1px solid rgba(255, 255, 255, 0.08)' }}>
|
|
<Group justify="space-between">
|
|
<Group gap="sm">
|
|
<ThemeIcon variant="light" color="red" size="lg" radius="md">
|
|
<TbBug size={20} />
|
|
</ThemeIcon>
|
|
<Text fw={700}>LATEST ERROR REPORTS</Text>
|
|
</Group>
|
|
<Button component={Link} to={appId ? `/apps/${appId}/errors` : '/bug-reports'} variant="subtle" size="compact-xs" color="blue" rightSection={<TbExternalLink size={14} />}>
|
|
View All Reports
|
|
</Button>
|
|
</Group>
|
|
</Box>
|
|
|
|
<ScrollArea>
|
|
<Table verticalSpacing="md" highlightOnHover className="data-table">
|
|
<Table.Thead bg="rgba(0,0,0,0.1)">
|
|
<Table.Tr>
|
|
<Table.Th px="xl">Error Message</Table.Th>
|
|
<Table.Th>Reporter</Table.Th>
|
|
<Table.Th>App Version</Table.Th>
|
|
<Table.Th>Timestamp</Table.Th>
|
|
<Table.Th pr="xl">Severity</Table.Th>
|
|
</Table.Tr>
|
|
</Table.Thead>
|
|
<Table.Tbody>
|
|
{isLoading ? (
|
|
<Table.Tr>
|
|
<Table.Td colSpan={5} align="center" py="xl">
|
|
Loading errors...
|
|
</Table.Td>
|
|
</Table.Tr>
|
|
) : bugs.length === 0 ? (
|
|
<Table.Tr>
|
|
<Table.Td colSpan={5} align="center" py="xl">
|
|
No errors found.
|
|
</Table.Td>
|
|
</Table.Tr>
|
|
) : bugs.map((error: any) => (
|
|
<Table.Tr
|
|
key={error.id}
|
|
onClick={() => handleRowClick(error)}
|
|
style={{ cursor: 'pointer' }}
|
|
>
|
|
<Table.Td px="xl">
|
|
<Text size="sm" fw={600} lineClamp={1}>{error.description}</Text>
|
|
</Table.Td>
|
|
<Table.Td>
|
|
<Badge variant="dot" color="brand-blue" radius="sm">{error.user?.name || error.userId || 'System'}</Badge>
|
|
</Table.Td>
|
|
<Table.Td>
|
|
<Text size="xs" fw={700} c="dimmed">{error.affectedVersion || 'N/A'}</Text>
|
|
</Table.Td>
|
|
<Table.Td>
|
|
<Group gap={6}>
|
|
<TbHistory size={12} color="gray" />
|
|
<Text size="xs" c="dimmed">{new Date(error.createdAt).toLocaleString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false })}</Text>
|
|
</Group>
|
|
</Table.Td>
|
|
<Table.Td pr="xl">
|
|
<Badge color={getSeverityColor(error.status)} variant="light" size="sm">
|
|
{(error.status || '').toUpperCase()}
|
|
</Badge>
|
|
</Table.Td>
|
|
</Table.Tr>
|
|
))}
|
|
</Table.Tbody>
|
|
</Table>
|
|
</ScrollArea>
|
|
</Paper>
|
|
|
|
<Drawer
|
|
opened={opened}
|
|
onClose={close}
|
|
position="right"
|
|
size="md"
|
|
title={
|
|
<Group gap="xs">
|
|
<TbMessageReport color="#ef4444" size={24} />
|
|
<Title order={4}>Error Investigation</Title>
|
|
</Group>
|
|
}
|
|
styles={{
|
|
header: { padding: '24px', borderBottom: '1px solid var(--mantine-color-default-border)' },
|
|
}}
|
|
>
|
|
{selectedError && (
|
|
<Stack p="lg" gap="xl">
|
|
<Box>
|
|
<Text size="xs" fw={700} c="dimmed" mb={4}>MESSAGE</Text>
|
|
<Text fw={700} size="lg" color="red">{selectedError.description}</Text>
|
|
</Box>
|
|
|
|
<SimpleGrid cols={2} spacing="lg">
|
|
<Box>
|
|
<Text size="xs" fw={700} c="dimmed" mb={4}>SOURCE</Text>
|
|
<Text fw={600}>{selectedError.source}</Text>
|
|
</Box>
|
|
<Box>
|
|
<Text size="xs" fw={700} c="dimmed" mb={4}>APP VERSION</Text>
|
|
<Badge variant="outline">{selectedError.affectedVersion || 'N/A'}</Badge>
|
|
</Box>
|
|
</SimpleGrid>
|
|
|
|
<Divider opacity={0.1} />
|
|
|
|
<Box>
|
|
<Group justify="space-between" mb="sm">
|
|
<Text size="xs" fw={700} c="dimmed">STACK TRACE</Text>
|
|
<Button
|
|
variant="subtle"
|
|
size="compact-xs"
|
|
color="gray"
|
|
onClick={() => setShowStackTrace((v) => !v)}
|
|
>
|
|
{showStackTrace ? 'Hide' : 'Show'}
|
|
</Button>
|
|
</Group>
|
|
{showStackTrace && (
|
|
<Code block color="red" style={{ whiteSpace: 'pre-wrap', lineHeight: 1.6, border: '1px solid var(--mantine-color-default-border)' }}>
|
|
{selectedError.stackTrace}
|
|
</Code>
|
|
)}
|
|
</Box>
|
|
</Stack>
|
|
)}
|
|
</Drawer>
|
|
</>
|
|
)
|
|
}
|
|
|
|
import { SimpleGrid, ThemeIcon } from '@mantine/core'
|